HelixML

CVE-2026-119618: The Critical Apache HTTP/2 RCE in mod_http2, Fixed in 2.4.71

Nov 12, 2026

CVE-2026-119618 is a critical, unauthenticated remote code execution flaw in Apache HTTP Server's mod_http2, affecting httpd 2.4.55 through 2.4.70 and fixed in 2.4.71. Who is actually affected, the root cause, the exposure numbers, and what to patch — with the version facts other write-ups got wrong.

On 11 November 2026 the Apache HTTP Server project released httpd 2.4.71 and disclosed three flaws fixed in it. The headline one is CVE-2026-119618, a thread-safety bug in mod_http2 that a remote, unauthenticated attacker can turn into heap corruption, and from there into remote code execution. Apache rates it critical; Red Hat scores it CVSS 9.8. It affects Apache HTTP Server 2.4.55 through 2.4.70, and only when the server is serving HTTP/2.

A body-less HTTP/2 request with a Content-Type header and no Content-Length makes mod_http2 synthesise a Content-Length of zero. In httpd 2.4.55 through 2.4.70 that header is written into the stream's shared table, which allocates from the connection's lock-free allocator while the connection thread is using the same free list, so two threads take the same memory node and the child heap is corrupted. In 2.4.71 the same write lands in the request-private clone and nothing is shared.

There are already good explainers on this one — Wiz published the exposure numbers everyone is quoting. This post is the version we wanted for ourselves: the facts checked against the primary sources, the root cause from the published diff, and the two version claims that other write-ups got wrong. Our own reproduction lab added the conditions between "the code path runs" and "the heap actually corrupts" that the advisory leaves out. We added the CVE to our public Threat Watch feed on the day it dropped; this is the long form of that record.

Everything below was verified on 12 November 2026 against the primary sources: the Apache advisory, the fix commit, the Red Hat CVE page, and the Debian tracker. Two reproduction labs informed it: the public googlesky lab, and a differential lab of our own, built the same day from the pinned upstream tarballs.

CVE-2026-119618 in one table

ProductApache HTTP Server, module mod_http2 (and the standalone mod_h2)
Affectedhttpd 2.4.55 – 2.4.70; standalone mod_h2 2.0.8 – 2.0.42
Fixed inhttpd 2.4.71 (released 2026-11-11)
SeverityCritical (Apache); CVSS 9.8 AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H (Red Hat). No NVD or CNA score yet.
CWE362 — concurrent execution using a shared resource without proper synchronisation
ReachRemote, unauthenticated
PreconditionThe server negotiates HTTP/2 (h2 or h2c) through the Protocols directive. The httpd default is http/1.1.
ExploitedNo in-the-wild reports; a public reproduction lab exists; not in CISA KEV
CreditAnthropic's OSS Scanner (ANT-2026-MAPKT6MZ); Anthony Weems and Simon Scannell, Google Cloud Vulnerability Research

Two more flaws ship their fix in the same 2.4.71 release, and they are worth a line because they load by default on many builds:

  • CVE-2026-106023 (important): a NULL pointer dereference in mod_md crashes the child process. Affects 2.4.30 – 2.4.70.
  • CVE-2026-109163 (low): a one-byte stack overwrite in mod_vhost_alias, affecting 2.4.69 and 2.4.70 — a follow-up to CVE-2026-63292.

The whole bug is one write to the wrong table

mod_http2 runs each HTTP/2 stream on a worker thread with its own private memory pool, so a worker never touches the connection thread's allocator. The connection's allocator is lock-free: it was created without a mutex, so there is nothing to serialise two threads reaching into it. The private-pool design is what keeps that safe.

assign_headers() breaks the rule for one request shape. When a request has no body, carries a Content-Type, and has no Content-Length, the function synthesises a Content-Length: 0 header. In 2.4.55 through 2.4.70 it writes that header into req->headers, the stream's shared table, instead of r->headers_in, the request-private clone the worker owns.

That write is where the threads collide. If the stream's header table is already full, the write allocates a new 8 KB node from the connection's lock-free allocator — at the same moment the connection thread is allocating from or freeing to the same free list. Two threads pop the same node, it ends up owned twice, and the child process heap is corrupted.

The fix changes the write target and nothing else:

-                apr_table_setn(req->headers, "Content-Length", "0");
+                apr_table_setn(r->headers_in, "Content-Length", "0");

r->headers_in lives in the worker's own request pool, so the synthesised header never crosses a thread boundary. That one line is the entire 2.4.71 fix for CVE-2026-119618.

The public reproduction lab that appeared on 12 November drives exactly this trigger in a container, shows the corruption on 2.4.70 under AddressSanitizer and ThreadSanitizer, and shows 2.4.71 clean under the same traffic. We built a differential lab of our own the same day — vulnerable and fixed builds from the pinned upstream tarballs, driven by a raw HTTP/2 client — and reproduced both verdicts independently.

Neither lab demonstrates a weaponised code-execution exploit. Red Hat's write-up goes further and states that "public research has demonstrated a reliable exploit against a default-configured server". The only public code proves memory corruption. Treat the step from corruption to code execution as credible, but not something you can download today. What our own runs added is in the next section.

What a reproduction adds: two conditions the advisory leaves out

Advisories say which versions are affected; they rarely say what makes the bug hit. Driving the trigger against our lab until it corrupted the heap, two conditions sat between "the code path runs" and "the heap corrupts", and neither is in the advisory.

The first is about size. The synthesised header grows the stream's table by 3840 bytes. That growth reaches the connection's allocator only when the stream pool's active 8 KB node has less than that free; otherwise the space is carved from the node, the two threads never meet, and nothing corrupts. We probed the running server at the vulnerable write and measured the node's remaining space as a function of the filler header size:

Filler value sizeNode space left at the writeThe push reaches the allocator?
48 B~6.9 KBno — no corruption in 425,000 requests
96–112 B1.3–1.9 KByes
128 B~0.5 KByes — deepest, most reliable
160–176 B4.8–6.1 KB (bigger node class)no

There is a narrow winning band of request shapes, and a naive "fill the table" reproducer silently lands outside it. For exposure that changes nothing — the attacker controls the shape and can search for the band — but for reproduction it is the difference between a lab that works and one that quietly proves nothing.

The second is about timing. Every completed request hands its 8 KB node back to the connection's allocator, so a steady drizzle of triggers keeps the free list deep, and both threads keep popping different nodes. The double-pop needs the list at depth 0–1 when two threads arrive together: bursts of header frames, hundreds of stream pools churning per connection, get it there constantly. With both conditions met, children started crashing within seconds; before them, a quarter-million requests achieved nothing.

Under gdb, the fault we captured when corruption landed was the connection thread — not the worker — dereferencing allocator=0x4141414141414141 inside libapr: a pointer slot overwritten with our own filler bytes ("A" = 0x41). The request's bytes ended up in a pointer the server follows.

That is the line between "destabilised the server" and "attacker-controlled memory reaches a followed pointer", and it is what makes the Red Hat statement about a reliable exploit credible. On the uninstrumented build the same traffic presents as a general protection fault inside libapr or an assertion abort inside mod_http2 — signatures worth knowing when triaging this stack.

One boundary we measured along the way: mod_http2 validates header values to visible ASCII. Values containing NUL bytes — or even high bytes ≥ 0x80 — get the stream reset. Any exploitation channel that wants binary bytes in the corrupted region has to arrive through request bodies, which shapes the engineering that follows the corruption step.

Is your server actually affected?

The version range is necessary, not sufficient. CVE-2026-119618 is only reachable when the server negotiates HTTP/2, and plenty of Apache installs never do. Check three things, in order:

# 1. Is the HTTP/2 module even loaded?
apachectl -M | grep http2
 
# 2. Is HTTP/2 actually turned on anywhere in the config?
#    (check inside every VirtualHost, not just the global scope)
grep -rniE 'Protocols.*h2c?' /etc/apache2 /etc/httpd
 
# 3. Does an internet-facing host negotiate it on the wire?
curl -sI --http2 https://your-host/ | head -1   # "HTTP/2 200" = h2 negotiated

If mod_http2 is not loaded, or no Protocols line names h2/h2c, the server serves HTTP/1.1 and this bug has no path to it. That is the real exposure question, and it is the one a plain "2.4.70 is affected" scanner result does not answer.

The exposure numbers, and two facts to ignore

Wiz scanned cloud estates and reported that 64% of cloud environments have at least one resource running an affected Apache version, and 51% have one exposed to the internet. Those figures are version-based — they count the 2.4.55–2.4.70 range, not whether HTTP/2 is turned on — so read them as the outer bound of the blast radius. They are still a fair argument for patching quickly: a critical, unauthenticated RCE in the most widely deployed web server is the kind of thing that gets a working exploit written against it, and the trigger is already public.

Two version claims are circulating that are wrong, and acting on either wastes time:

  • There is no fixed standalone mod_h2 release. Some write-ups (and the reproduction lab's own README) list "mod_h2 2.0.43" as the fix, and Wiz's table lists "2.0.24". The icing/mod_h2 repository was archived in June 2026 with v2.0.42 as its final tag, and 2.0.24 sits inside the affected range. If you build mod_h2 from source, there is no newer module to move to — the fix lives in httpd 2.4.71's bundled mod_http2.
  • "Disable mod_http2" is not the only lever. You do not need to unload the module; removing h2 and h2c from your Protocols directives and reloading is enough to close the path, and it is the workaround the lab author verified.

What one RCE reaches from a pod

The version table answers who can get in. What they find after getting in is a question about the deployment — and for the internet-exposed half of Wiz's numbers, the common answer is a pod carrying an identity. Assume the exploit lands (the corruption is proven; the reliable weaponisation is credible but not public) and follow the path from there.

One unauthenticated HTTP/2 request corrupts the heap of an internet-facing Apache 2.4.55–2.4.70 pod serving static files in Kubernetes and runs code as the httpd user. From that one pod the path forks into two identities: the Kubernetes service account token mounted at /var/run/secrets/kubernetes.io/serviceaccount/token — world-readable by default, automount on by default — which talks to kubernetes.default.svc under whatever RBAC the pod's service account has; and, under GKE Workload Identity, a GCP service-account token from metadata.google.internal that inherits the bound SA's IAM roles with no Kubernetes RBAC in between. Amber notes mark the caps: automount off, RBAC scope and an egress policy for the cluster identity; a per-workload SA and metadata egress policy for the cloud identity.

The first read is the cluster identity. Kubernetes mounts the pod's service account token at /var/run/secrets/kubernetes.io/serviceaccount/token, and automountServiceAccountToken defaults to on — so the token sits inside a static file server that needs no API access at all. Any user in the container can read it, the httpd worker included.

The stolen JWT talks to kubernetes.default.svc, which every pod can reach unless an egress policy blocks it. RBAC decides what the token is worth: the default service account can only enumerate its own permissions; a shared or over-privileged one reads Secrets, execs into other pods, or creates new ones. On clusters that still issue legacy tokens, the JWT does not expire while the pod lives.

The second read is the cloud identity, and it skips Kubernetes entirely. On GKE with Workload Identity there are no key files to steal; the pod asks metadata.google.internal for a token and receives one for whatever GCP service account the workload is bound to. The attacker inherits that account's IAM roles verbatim. For a frontend pod that is often just object storage read — and routinely more, because one service account ends up bound to several workloads.

The chain needs no egress channel of its own. The compromised server is itself a static file server: the payload writes into the document root, and the attacker collects it through the front door. Ports 80 and 443, nothing else open.

Each step has a cap, and none of the caps are exotic. automountServiceAccountToken: false removes the cluster identity from a pod that needs none. A dedicated, least-privilege GCP service account shrinks the cloud identity to the bucket it actually reads. A NetworkPolicy blocking pod egress to the API server and the metadata endpoint caps both at once. The only control that removes the first step is still the patch — 2.4.71, or Protocols http/1.1 where HTTP/2 is not needed.

We ran the same audit against our own estate while writing this. helix.ml is a Go binary serving a static export — no mod_http2, no path. Our PTaaS login is Keycloak, the subject of the earlier post. The pattern to audit is any Apache in a cluster fronting anything: the three checks above say whether the bug has a path; the caps say what is on the other side.

Distro status: don't assume your package manager has it yet

The fix landed upstream on 11 November. Downstream packages lag, so confirm the build you are about to deploy actually carries it.

SourceStatus on 2026-11-12
Apache httpdFixed in 2.4.71
DebianFixed only in sid (2.4.71-1); bookworm, trixie, forky still vulnerable
Red Hat RHEL 9 / 10Rated Important (their mod_http2 gives each stream its own allocator, raising exploit complexity); errata tracked on the CVE page
Red Hat RHEL 8 and earlierNot affected (the vulnerable code is not present before mod_http2 2.0.8)

The Red Hat nuance is useful even if you do not run RHEL: an allocator-per-stream build is materially harder to exploit than the shared-allocator default, which is why the same bug is critical upstream and "important" on RHEL 9/10.

What to do

  • Patch to httpd 2.4.71. It fixes CVE-2026-119618 along with the mod_md and mod_vhost_alias flaws. Before you trust a distro package, confirm it is 2.4.71-based — on 12 November, Debian stable was not.
  • If you cannot patch now and do not need HTTP/2, remove h2 and h2c from every Protocols directive, including the ones inside VirtualHost blocks, and reload. Servers left on the http/1.1 default are not affected.
  • Find your HTTP/2-enabled, internet-facing hosts first with the three checks above, and patch those before anything behind authentication or a WAF.
  • Standalone mod_h2 users: move to 2.4.71's bundled module. There is no standalone fix release.
  • Watch for a CVE score and distro errata. NVD had no score at the time of writing; Red Hat's 9.8 is the number to plan around.
What is CVE-2026-119618?
A critical, unauthenticated remote code execution vulnerability in the mod_http2 module of Apache HTTP Server. A thread-safety flaw in assign_headers() lets a worker thread write into memory owned by the connection thread, corrupting the child process heap. It affects httpd 2.4.55 through 2.4.70 and is fixed in 2.4.71.
Which Apache versions are affected, and which fixes it?
Apache HTTP Server 2.4.55 through 2.4.70 are affected, as is the standalone mod_h2 from 2.0.8 through 2.0.42. The fix is httpd 2.4.71, released 11 November 2026. There is no fixed standalone mod_h2 release — the repository was archived at v2.0.42 — so move to httpd 2.4.71's bundled mod_http2.
Is my Apache server affected if HTTP/2 is off?
No. CVE-2026-119618 is only reachable when the server negotiates HTTP/2 (h2 or h2c) through the Protocols directive. The httpd default is http/1.1. Check with 'apachectl -M | grep http2' and by grepping your config for Protocols lines naming h2 or h2c.
Is there a public exploit for CVE-2026-119618?
A public reproduction lab exists (googlesky/CVE-2026-119618) that proves the heap corruption on 2.4.70 under AddressSanitizer and ThreadSanitizer and shows 2.4.71 clean. Our own differential lab reproduced both verdicts the same day, and measured what the advisory leaves out: the trigger only corrupts the heap when the stream pool's memory node is nearly exhausted at the write and the allocator's free list is shallow, so a naive 'fill the table' reproducer silently fails. It is a crash and root-cause demonstration, not a weaponised RCE. Red Hat states public research has shown a reliable exploit; no in-the-wild exploitation has been reported, and it is not in the CISA KEV catalog as of 12 November 2026.
If my Apache runs in Kubernetes, what can an attacker reach after the RCE?
The bug gets code execution inside the pod; the pod's identity decides the rest. By default the pod's Kubernetes service account token is mounted world-readable, so the attacker can call the API server under that account's RBAC — the default account can only enumerate, a shared or over-privileged one can read Secrets or exec into pods. On GKE with Workload Identity, the pod can also exchange its identity for a GCP service-account token from the metadata server and inherit that account's IAM roles. automountServiceAccountToken: false, per-workload least-privilege service accounts, and egress NetworkPolicies each cap a step; httpd 2.4.71 removes the first step.
How severe is it?
Apache rates it critical. Red Hat scores it CVSS 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) and rates RHEL 9 and 10 'Important' because their builds give each HTTP/2 stream its own allocator. RHEL 8 and earlier are not affected. NVD had no score at the time of writing.

We track vulnerabilities like this one on Helix Threat Watch, a public feed with the preconditions stated plainly, so "affected" never oversells what it means for your estate. The CVE-2026-119618 record carries the sources, the timeline, and the remediation steps above.

Helix Cyber runs agent-driven penetration tests that reason about which of your hosts actually negotiate HTTP/2, instead of flagging a version string and calling it a finding. Every candidate finding is reproduced by a second agent before it reaches a report, and retests after a patch like 2.4.71 are a click rather than a new engagement.