CVE-2026-92392: Verified Reproduction
CVE-2026-92392: curl HTTP/2 upload Use-After-Free RST STREAM HTTP 1 1 REQUIRED/REFUSED STREAM leading to potential RCE
CVE-2026-92392 is verified against the affected target. Vulnerability class: RCE. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00386.
What Is CVE-2026-92392?
CVE-2026-92392 is a high-severity RCE vulnerability. Pruva has independently reproduced it and publishes a verified, runnable proof-of-concept (reproduction REPRO-2026-00386).
CVE-2026-92392 Severity
CVE-2026-92392 is rated high severity.
High — serious impact or readily exploitable. Prioritize remediation.
How to Reproduce CVE-2026-92392
pruva-verify REPRO-2026-00386 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00386/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-92392
- reached the target end-to-end
- full exploit chain demonstrated
- crash observed
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
A malicious TLS/HTTP/2 TCP peer controls flow-control stalls, REFUSED_STREAM resets, HPACK headers, TLS session tickets used for heap grooming, resurrected-stream window updates, and the final fake Curl_easy callback trigger while the victim runs curl -Z -T on a 586035-byte file.
- RST_STREAM retry
- vulnerable multi_done ordering flushes queued upload bytes as a new h2 request
- orphaned stream retains freed Curl_easy pointer
- TLS-ticket/HPACK heap grooming reclaims and corrupts it
- forged-stream over-read discloses current libc/libcurl bases
- fake Curl_easy sets fdebug to fresh libc system()
- invalid frame callback executes id>mk_<nonce> in the curl process
How the agent worked
Root Cause and Exploit Chain for CVE-2026-92392
curl 8.22.0 contains a use-after-free in the HTTP/2 upload retry path. When a malicious HTTP/2 peer resets an active upload with HTTP_1_1_REQUIRED or REFUSED_STREAM, libcurl converts the reset into a retry. In the vulnerable cleanup order, curl first tells the connection filters that the transfer is done and then lets request cleanup flush buffered upload bytes. Those bytes can be reinterpreted as a new HTTP/2 request, creating an unintended stream whose user data is the soon-to-be-freed Curl_easy. This run reproduced the complete disclosed exploit chain through the real curl tool: a malicious peer reclaimed and corrupted the freed object, induced a 99,056-byte forged-stream heap over-read, recovered fresh current-process libc and libcurl addresses despite ASLR, replaced the object with a fake Curl_easy, and triggered its attacker-selected debug callback as libc system(), creating a verifiable target-local marker.
- Affected package/component: curl/libcurl HTTP/2 upload and retry handling with nghttp2.
- Affected versions: curl 8.22.0 and earlier according to the disclosed advisory. The vulnerable target used here is the exact curl 8.22.0 commit
01346829096c61b372692f6dc43ffa778c6caccd. - Fixed control: commit
8b0744af5b1b631da5b938ca31c3f9895ad5a321(multi: adjust the cleanup order in multi_done). - Risk level: High. A malicious HTTP/2 server can execute a command in the victim curl process when the victim uploads a suitable file. The exploit needs a large upload containing request-like text; the demonstrated ASLR-enabled chain also uses curl parallel mode (
-Z) and a heap-layout-calibrated runtime. - Consequence: Remote command execution with the privileges and confinement of the curl process. The marker commands in the final isolated proof were
echo Vv1>mk_noneandecho Vv2>mk_none.
Impact Parity
- Disclosed/claimed maximum impact: Remote code execution (
code_execution). - Reproduced impact from this run: Attacker-induced command execution through the production curl CLI and TCP/TLS/HTTP/2 boundary. Two fresh vulnerable processes created unique distinct marker files after independently leaking their randomized libc/libcurl bases. Two fixed processes reached the same malicious reset/stall path but created no resurrected stream, disclosure, callback trigger, or marker.
- Parity:
full. - Not demonstrated: The marker command is intentionally harmless and does not establish persistence or privilege escalation. Reliability outside the pinned exploit runtime was not claimed; the underlying UAF is broader than this heap-grooming calibration.
Root Cause
The failure is an ordering bug in multi_done() combined with HTTP/2 stream user data retaining a raw Curl_easy *:
- curl uploads a large file over HTTP/2. The peer limits stream flow control so an upload remainder stays queued.
- The malicious peer replies and sends
RST_STREAM(HTTP_1_1_REQUIRED or REFUSED_STREAM), causing libcurl to schedule a retry. - Vulnerable
multi_done()callsCurl_conn_ev_data_done(data, premature)beforeCurl_req_done(&data->req, data, premature). - Connection-filter teardown makes curl forget the original transfer/stream association. Request teardown then flushes the buffered upload remainder into the still-live connection.
- If those file bytes parse as an HTTP request head,
h2_submit()opens a new/resurrected stream and nghttp2 stores the rawCurl_easy *as stream user data. - The normal retry completes. The curl tool frees the easy handle, but the pooled HTTP/2 connection and resurrected stream retain the stale pointer.
- The exploit uses attacker-controlled TLS 1.3 session-ticket sizes and HPACK header allocations to reclaim the freed 3,368-byte
Curl_easy, then corrupt abuf_chunkassociated with the orphaned stream. A window update makes curl transmit bytes outside the intended upload buffer. - The peer receives 99,056 leaked heap bytes and identifies
main_arenaand libcurl pointers. Those per-process values resolve libcsystem()under ASLR. - A final session ticket reoccupies the stale object with a fake
Curl_easy: the command string begins at offset zero,set.fdebugpoints to the freshly resolvedsystem(), and the verbose flag forces the callback path. An invalid HTTP/2 frame invokesCurl_failf()/tracing through the fake callback, executing the marker command.
The disclosed exploit was originally calibrated with ZT_V1=34660, which leaked only upload prose in this runtime. Live heap analysis showed the current unsorted-piece geometry was smaller; changing only that first carve value to ZT_V1=32400 preserved the intended 0xc50 remainder. Adjacent controls (32160 and 32640) failed to disclose pointers, while 32400 succeeded repeatedly.
The upstream fixed commit is https://github.com/curl/curl/commit/8b0744af5b1b631da5b938ca31c3f9895ad5a321. It moves Curl_req_done() before Curl_conn_ev_data_done() so request bytes are finished while the connection filter still knows the transfer, and then explicitly tells filters to give up references before detaching the connection. In this run, the fixed build never created the resurrected stream.
Reproduction Steps
Run
bundle/repro/reproduction_steps.shfrom any directory.PRUVA_ROOTmay be set to the bundle path.The script resolves the prepared project cache, verifies the exact vulnerable and fixed source commits and cleanup-order hunk, and verifies or rebuilds a pinned Ubuntu 24.04 exploit image containing stock non-ASAN curl 8.22.0, nghttp2 1.59.0, OpenSSL 3.0.13, and glibc 2.39.
It builds the exact fixed commit with the same compiler, flags, dependencies, and runtime closure.
For each attempt, the script starts the real malicious TLS/HTTP/2 TCP peer and invokes the actual curl tool as:
curl -Z -T sample_docs.md https://127.0.0.1:<port>/x https://127.0.0.1:<port>/yIt runs two isolated vulnerable attempts and requires each to show a resurrected stream, a full 115,439-byte forged transfer, a 99,056-byte leaked-memory artifact, independently resolved libc/libcurl bases, the final fake-object callback trigger, and an actual
marker.txtcopied from the curl process's working directory.It runs two isolated fixed attempts with the same peer and packet sequence and requires each to reach the HTTP/2 reset/stall path without resurrection, disclosure, callback trigger, or marker.
It verifies that the two vulnerable libc/libcurl bases differ, proving that ASLR is active and that addresses were reacquired from each live process rather than reused as constants.
Expected final output is:
[+] CONFIRMED: malicious HTTP/2 peer achieved command execution in curl 8.22.0 (2/2); fixed commit blocked the chain (2/2)
Evidence
The final script was run twice consecutively and passed both times. The latest immutable proof is bound by bundle/repro/runtime_manifest.json.
Vulnerable process 1
Peer transcript:
bundle/repro/rce-vuln-1/server.logcurl transcript:
bundle/repro/rce-vuln-1/victim.logLeaked bytes:
bundle/repro/rce-vuln-1/leak.bin(99,056 bytes)Actual command marker:
bundle/repro/rce-vuln-1/marker.txtMarker value:
Vv1Key peer evidence:
*** RESURRECTED stream 5 ... Z9: pulled 115439 bytes of DATA on S'=5 (wanted 115439) Z9: leaked 99056 bytes Z9: libc_base=0x7f8bb0400000 by 5 vote(s) ... system=0x7f8bb0458750; libcurl_base=0x7f8bb0e4b000 Z10: sent NST(final fake, 2832 B, fdebug=0x7f8bb0458750, cmd=b'echo Vv1>mk_none') ...
Vulnerable process 2
Peer transcript:
bundle/repro/rce-vuln-2/server.logLeaked bytes:
bundle/repro/rce-vuln-2/leak.bin(99,056 bytes)Marker:
bundle/repro/rce-vuln-2/marker.txtIndependently randomized/disclosed addresses:
libc_base=0x7fa11e705000 libcurl_base=0x7fa11f150000 system=0x7fa11e75d750
These differ from vulnerable process 1. /proc/sys/kernel/randomize_va_space was 2; the exploit did not read /proc in the final proof.
Fixed negative controls
bundle/repro/rce-fixed-1/server.logbundle/repro/rce-fixed-2/server.logbundle/repro/rce-fixed-1/victim-cwd-ls.txtbundle/repro/rce-fixed-2/victim-cwd-ls.txt
Both controls show the listener, TLS/HTTP/2 preface, 586,035-byte upload, first reset, retry, and second stalled upload. For example:
conn0: learned 586035 body bytes ... RST_STREAM(REFUSED_STREAM)
conn1: A#2 stalled at 360447 body bytes -> trigger refused
Neither control contains RESURRECTED, Z9, or Z10; each victim directory is empty and marker-name.txt contains only a newline.
Build and mitigation identity
bundle/logs/repro/rce-vulnerable-identity.log: curl 8.22.0 executable/library/libc checksums and version.bundle/logs/repro/rce-vulnerable-mitigations.log: PIE (ET_DYN), NX stack, RELRO, symbols, build ID, and loader resolution.bundle/logs/repro/rce-fixed-mitigations.log: corresponding fixed-build evidence.bundle/logs/repro/randomize_va_space.log: kernel ASLR setting.- Vulnerable executable SHA-256:
ee31384e2df166f57d7dbdf3f8a7f9f368a38f8ddcf8ef4e80ca038c06931ae7. - Vulnerable libcurl SHA-256:
ee986196347fe90ba808c6fa5dcde03ce4d27a08a16fa339b454a74e6785ef53. - glibc SHA-256:
d8db8739a1633c972cec6a4fe0566bdcec6fd088f98723492ab0361f66238f75. - Fixed ELF SHA-256:
216e8087e5b4a496d76eb3133687c6a748ce7da1dc7cc808de7a1e001393d68f. - Fixed libcurl SHA-256:
74b322c9f6492924604a8546d3c0302a8be05f02a544b5f0425c2ff7c334f53d.
Recommendations / Next Steps
- Upgrade to curl 8.23.0 or another release containing commit
8b0744af5b1b631da5b938ca31c3f9895ad5a321. - For backports, ensure
Curl_req_done()occurs beforeCurl_conn_ev_data_done()and that connection filters release allCurl_easyreferences before the object can be detached/freed. - Add regression tests for both
HTTP_1_1_REQUIREDandREFUSED_STREAM, large flow-controlled uploads, retry paths, request-like patterns at multiple upload offsets, parallel transfers, connection-pool shutdown, and frame delivery to stale stream IDs. - Exercise those tests with ASAN and non-sanitized hardened builds.
- Consider replacing raw
Curl_easy *nghttp2 stream user data with a validated identifier/lookup so a stale stream cannot directly retain an object address.
Additional Notes
- The final production proof uses no sanitizer and keeps certificate verification enabled. The malicious peer certificate is signed by an isolated test CA installed in the pinned container.
- The reproducer is idempotent and passed twice consecutively, with two fresh vulnerable and two fresh fixed processes per invocation.
- The vulnerability trigger itself is not tied to this exact heap. Full command execution is allocator/build sensitive, so the reproducer pins the target closure and verifies its binary hashes before running.
- Later glibc
double free or corruptionaborts are expected after the marker has been created because the exploit deliberately corrupts allocator/object state. Marker creation and fixed negative controls, not the final abort, are the command-execution oracle. - The final bounded marker commands are
echo Vv1>mk_noneandecho Vv2>mk_none; each is at most 23 bytes because the fake object'smultifield begins at offset 24. Fixed attempts receive distinct candidate commandsecho Vf1>mk_noneandecho Vf2>mk_none, but create no file.
CVE-2026-92392 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
ls -la /pruva/project-cache/ 2>/dev/null; ls /pruva/project-cache/repo 2>/dev/null | head; ls /pruva/project-cache/repo-mirrors 2>/dev/null; cat /pruva/project-cache/.pruva/cache_manifest.json 2>/dev/nulltotal 4
drwx------ 4 vscode vscode 80 Oct 14 14:38 .
drwxr-xr-x 3 root root 4096 Oct 14 14:38 ..
drwx------ 2 vscode vscode 60 Oct 14 14:38 .pruva
drwx------ 2 vscode vscode 40 Oct 14 14:38 repo-mirrors
{
"schema_version": 1,
"entries": [curl -sL https://curl.se/docs/CVE-2026-92392.json | head -c 3000{"cwd":"/workspace","duration_ms":178,"exit_code":0,"stderr":"","stdout":"{\n \"schema_version\": \"1.5.0\",\n \"id\": \"CURL-CVE-2026-92392\",\n \"aliases\": [\n \"CVE-2026-92392\"\n ],\n \"summary\": \"HTTP/2 upload Use-After-Free\",\n \"modified\": \"2026-09-17T12:57:32.00Z\",\n \"database_specific\": {\n \"package\": \"curl\",\n \"affects\": \"both\",\n \"URL\": \"https://curl.se/docs/CVE-2026-92392.json\",\n \"www\": \"https://curl.se/docs/CVE-2026-92392.html\",\n \"issue\": \"https://hackerone.com/reports/4030333\",\n \"CWE\": {\n \"id\": \"CWE-416\",\n \"desc\": \"Use After Free\"\n },\n \"last_affected\": \"8.22.0\",\n \"severity\": \"High\"\n },\n \"published\": \"2026-10-14T08:00:00.00Z\",\n \"affected\": [\n {\n \"ranges\": [\n {\n \"type\": \"SEMVER\",\n \"events\": [\n {\"introduced\": \"8.12.0\"},\n {\"fixed\": \"8.14.3\"},\n {\"introduced\": \"8.15.0\"},\n {\"fixed\": \"8.16.2\"},\n {\"introduced\": \"8.17.0\"},\n {\"fixed\": \"8.20.2\"},\n {\"introduced\": \"8.21.0\"},\n {\"fixed\": \"8.2… [truncated]Artifacts and Evidence for CVE-2026-92392
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-92392
FAQ: CVE-2026-92392
Is CVE-2026-92392 exploitable?
How severe is CVE-2026-92392?
What type of vulnerability is CVE-2026-92392?
How can I reproduce CVE-2026-92392?
Is the CVE-2026-92392 reproduction verified?
References for CVE-2026-92392
Authoritative sources for CVE-2026-92392 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.