Skip to content
Exploit for CVE-2026

Exploit for CVE-2026

Sploitus • November 8, 2026

# CVE-2026-124040 — CivetWeb HTTP/2 HPACK header poisoning leads to conditional RCE

**NVD:**

**CVSS:** 8.1 (High) `CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H` · 9.2 (Critical) `CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N`

A use-after-free in CivetWeb's HTTP/2 request handling lets an unauthenticated

remote client corrupt HPACK dynamic-table header names. A stale `request_uri`

pointer is dereferenced after the request header list has been freed; if the

freed allocation has been reused for a live HPACK header-name copy containing

`?`, `handle_request()` performs a one-byte NUL write that truncates the name.

A name registered as `:path?x`, `:method?x`, or `x-admin?x` therefore becomes

`:path`, `:method`, or `x-admin`. A later HEADERS frame containing only indexed

references is interpreted as an attacker-chosen request such as

`PUT /admin/exec` or `x-admin: 1`, while a frame-forwarding front-end only sees

benign literal headers and does not block the request.

When the backend application trusts the effective method/path or a

front-end-injected security header, this results in pre-authentication command

execution. Stock CivetWeb does not ship such a privileged callback, so the RCE

is deployment-dependent; the underlying primitive also bypasses literal-header

| 588860e3 (master snapshot 2026-04-19) | Vulnerable code present |

| v1.13–v1.15 | HTTP/2 stale-state crash reproduced; RCE chain not verified |

| v1.12 and earlier | Relevant HTTP/2 path absent |

`src/http2.inl` retains HPACK-decoded string pointers in request state

(`request_uri`, `local_uri`, `local_uri_raw`, `request_method`) and calls

`free_buffered_request_header_list()` without invalidating them. A subsequent

HEADERS frame can reuse those dangling pointers.

The write occurs in `handle_request()` in `src/civetweb.c`:

if ((conn->request_info.query_string = strchr(ri->request_uri, '?')) != NULL) {

*((char *)conn->request_info.query_string++) = '\0';

In the demonstrated glibc tcache layout, the freed `:path` allocation is reused

for an HPACK dynamic-table entry name. The resulting NUL write turns

`:path?x` into `:path`, so an indexed reference such as `0xBE 0xBF` supplies

`:method PUT` and `:path /admin/exec` without their literal names appearing in

The demonstrated command execution requires all of the following:

1. A CivetWeb build with TLS/ALPN and the HTTP/2 processing path enabled.

2. A front-end that inspects literal header representations but forwards

3. A backend callback that performs a privileged action based on the effective

method/path or a trusted front-end security header.

4. Request and allocator state that permit the stale-pointer reuse and HPACK

CIVETWEB_DIR=/path/to/vulnerable/civetweb ./verify.sh

CIVETWEB_DIR=/path/to/vulnerable/civetweb MODE=header ./verify.sh

[PASS] hidden PUT /admin/exec bypassed the literal-header ACL; callback executed id

[PASS] hidden x-admin header bypassed the literal-header ACL; callback executed id

`admin_server.c` is a deliberately privileged demonstration application. It

runs `id` when it receives `PUT /admin/exec` or `x-admin: 1`; this callback is

not part of CivetWeb. `proxy.py` is a frame-forwarding front-end that blocks

the literal values but passes indexed references unchanged.

CIVETWEB_DIR=/path/to/patched/civetweb EXPECT_SAFE=1 ./verify.sh

CIVETWEB_DIR=/path/to/patched/civetweb EXPECT_SAFE=1 MODE=header ./verify.sh

- resets per-request state when a new HTTP/2 HEADERS frame starts a request,

- clears `local_uri_raw` in `reset_per_request_attributes()`,

- rejects HTTP/2 requests missing mandatory `:method` / `:path` pseudo-headers.

Extracted Entities

Attack Types (1)

CWE Weaknesses (1)

Tools (1)