What matters:
If you are a Pentera customer, you’re already covered and can Validate CVE-2026-119618 exposures in your environment.
- The flaw is in the HTTP/2 module and is only reachable where HTTP/2 is switched on. A server whose Protocols line offers neither h2 nor h2c never runs the vulnerable code, whatever its version.
- Exploitation is unauthenticated and remote. The trigger is a dull-looking request: no body, a Content-Type, no Content-Length, and enough headers to fill a table.
- Apache lists httpd 2.4.55 through 2.4.70 as affected. Your distribution’s version string may disagree, in either direction. Ubuntu shipped fixes for packages numbered 2.4.41 and 2.4.52.
- The public evidence so far is heap corruption and crashes, not code execution. Red Hat says a reliable exploit has been demonstrated in public research; the Cloud Security Alliance went looking for it and says it could not find it. No exploitation in the wild has been reported, and the CVE is not in CISA’s KEV catalogue as of 13 November.
Security teams: upgrade to httpd 2.4.71 or your distribution’s fixed package, restart, and confirm what is loaded. If you cannot patch today, remove h2 and h2c from Protocols. Decide by your vendor’s advisory, not by the upstream version range.
One write to the wrong table
Apache’s advisory spends one sentence on the bug. The fullest public account is the lab write-up by googlesky, which reconstructs the mechanism from the 2.4.70 source and backs it with sanitizer traces. This section leans on it throughout. Red Hat’s description of the root cause matches it at the top level; the detail is the lab’s reading, and Apache has not confirmed it.
mod_http2 processes each request on a worker thread, and it goes to some trouble to keep those workers away from the connection’s memory. The connection’s pools draw on an APR allocator that has no lock, which is fine for as long as one thread uses it. So a worker gets its own private allocator, and assign_headers() starts by cloning the stream’s header table into the request. From then on the worker reads and writes its own copy.
With one exception. If a request has no body, carries a Content-Type and has no Content-Length, the function tidies up by adding Content-Length: 0. For almost four years it added it to the original:
/* If we have a content-type, but already seen eos, no more * data will come. Signal a zero content length explicitly. */ - apr_table_setn(req->headers, "Content-Length", "0"); + apr_table_setn(r->headers_in, "Content-Length", "0");
That diff is the whole fix. req->headers belongs to the stream. It lives in a pool backed by the connection’s allocator, and it was being written by a thread that was never supposed to touch it.
Most of the time nothing happens, because adding a header to a table with spare room allocates nothing. The write only reaches the allocator when the table is full. It starts with room for 10 entries and doubles each time it fills, and by the lab’s arithmetic the practical case is 80: 79 headers from the client plus the Host entry the server adds itself. Header number 81 forces the table to grow, and the memory for that comes off the connection’s free list, on the worker’s thread, while the connection thread is using the same list with no lock. Nothing stops both threads from claiming the same block. From then on two unrelated pools each believe they own it, and whatever either one writes lands in the other’s data.
The write up also points out that the rule being broken is written down in the module itself. A source comment explains that request processing gets its own allocator because that is the only way it can safely run in another thread. The rule was sound. It was also enforced by nothing except everyone remembering it.
“Affected” is a version string
Apache’s advisory says 2.4.55 through 2.4.70. That is true of Apache’s own tarballs. Follow it into the places most people get their httpd from and it comes apart three ways.
Below the range, and vulnerable. Ubuntu published fixes for 22.04 and 20.04, whose apache2 packages are numbered 2.4.52 and 2.4.41. Ubuntu’s security team explains why in a one-line note: newer versions of the module were backported into stable releases. The httpd number stayed put while the mod_http2 underneath it moved. A scanner (or a person) comparing “2.4.52” against “2.4.55 through 2.4.70” files those servers under not affected.
Inside the range, and a different bug. RHEL 9 and 10 ship affected mod_http2 builds, and Red Hat rates them Important, not Critical, because those builds allocate each HTTP/2 stream from a separate allocator. The wrong-table write still happens. What it races against is different, and Red Hat says that substantially raises the complexity of exploiting it. Same CVE, same line of C, and a different answer to “how bad is this for me”, decided by a build choice you cannot read off a version string.
Inside the range, and unreachable. The vulnerable code runs only for HTTP/2 requests. Upstream, HTTP/2 stays off until a Protocols directive turns it on. A 2.4.70 server that speaks only HTTP/1.1 is affected on paper and out of reach in practice.
Read the headline exposure number again with that in mind. Wiz reported that 64% of cloud environments contain a resource running a vulnerable version, and that 51% have one publicly exposed. Wiz also said, right next to those figures, that they do not take configuration into account. So it is a count of version strings, and an honest one. The number of servers an attacker can reach with this bug is some fraction of it, and nobody has published the fraction.
If you build the standalone mod_h2 from GitHub, Apache’s advisory gives you one line: “Affected releases of mod_h2 from GitHub: from 2.0.8 through 2.0.42.” It names no fixed standalone release.
None of this is exotic. A version string is a label on a build, and this week the label misled in three places: Ubuntu, RHEL, and any server that never switched HTTP/2 on. Whether you are exposed depends on which listener negotiates h2, on which build, with which allocator. You find that out by looking at the machine.
RCE on the label, crashes in the evidence
The CVE title says RCE. Red Hat goes a step further and says a reliable exploit against a default-configured server has been demonstrated in public research. It names no researcher and links nothing. As of 13 November, what is public is one lab. On 2.4.70 it shows AddressSanitizer crashes and 31 ThreadSanitizer reports of the allocator race, each naming assign_headers(). On 2.4.71, under the same traffic, it shows none. It ran on sanitizer builds, under the prefork MPM, over cleartext h2c. It proves the corruption. Code execution is another matter: the lab never gets there, and its own impact line says “potential”.
My working assumption is that the label is right and the public evidence is simply behind it. Attacker-timed heap corruption in a network daemon has a long record of ending in code execution, and Red Hat chose its words. Last month we wrote that “too hard to exploit” has an expiry date. “No public exploit” has one too.
The distinction still changes one thing: what you tell people. “Critical RCE, exploit exists” and “critical memory corruption, RCE asserted, no public exploit located” produce the same patch ticket and different sentences in the incident channel. Use the second until the first is true.
Two days in plain sight
The fix was merged to httpd trunk on 9 November at 01:51 UTC, and to the 2.4.x branch about an hour later. The commit is titled “mod_http2: assign_headers update table”. It has no description and no mention of security. The advisory followed on 11 November. For more than two days, the complete fix for a critical was a public one-line diff on GitHub.
This is no complaint about Apache. An open project has to put the fix somewhere before it can ship it, and a boring commit title is the traditional way to buy a little quiet. The question is what that quiet is worth now. In the PHP2Shell post we argued that public source plus a model is a decent substitute for a leaked write-up. Here nobody needed a substitute. A one-line change that moves a write from a shared table to a private copy, inside a threaded module, more or less answers “why?” by itself. Asking “why?” of every commit to a project like httpd used to cost an analyst’s attention. It does not any more.
I have no evidence anyone used the window. But look at who found the bug. Anthropic’s OSS Scanner reported it on 26 October. Anthony Weems and Simon Scannell of Google Cloud Vulnerability Research reported it, independently, on 3 November. When two parties find the same bug within eight days of each other, after it sat in every release since January 2023, it is fair to wonder about a third who filed nothing. Nothing public answers that.
By my count from Apache’s own advisory page, httpd has fixed 47 CVEs across four releases in 2026, against 14 in all of 2025, and the acknowledgements now name scanners and models alongside people.
What to do, and what not to trust
Patch. It is the only complete fix.
| Where your httpd comes from | Fixed in |
|---|---|
| Apache source release | httpd 2.4.71 |
| Ubuntu 26.10 | apache2 2.4.68-1ubuntu6.2 |
| Ubuntu 26.04 LTS | apache2 2.4.66-2ubuntu2.8 |
| Ubuntu 24.04 LTS | apache2 2.4.58-1ubuntu8.19 |
| Ubuntu 22.04 LTS | apache2 2.4.52-1ubuntu4.27 |
| Ubuntu 20.04 LTS (ESM) | apache2 2.4.41-4ubuntu3.23+esm11 |
| RHEL 10 | mod_http2-2.0.29-5.el10_3.1 |
| RHEL 9 | mod_http2-2.0.26-7.el9_9.1 |
| Debian 13 | apache2 2.4.71-1~deb13u1 |
Ubuntu 18.04 and older, and RHEL 8 and older, are not affected: their mod_http2 predates the code.
Three traps. First, the package that changes may not be the one you are watching. On RHEL the fix ships in mod_http2, and the httpd version does not move. Second, patched on disk is not patched in memory. Restart httpd and check what is loaded. Third, httpd hides in the same places PHP did last month: container base images, control panels, appliances, and products that bundle it without saying so.
If you cannot patch yet, take HTTP/2 away. Set Protocols http/1.1, with no h2 and no h2c, and check every virtual host, because Protocols can be set per vhost. Apache’s advisory lists no workaround; this one comes from Red Hat, Wiz and the lab, whose mitigation test shows the server refusing the HTTP/2 preface.
What not to trust:
- The upstream range, for the reasons above.
- The Server banner. If it shows a version at all, it shows the httpd number, which is the wrong number on Ubuntu and RHEL.
- A WAF, a CDN or a header-count limit. Whether any of them stops the trigger shape is untested in anything public. The header table also fills at 10, 20 and 40 entries, and nobody has published what those smaller boundaries do on a vulnerable build.
- Second-hand detection advice. No indicators of compromise have been published. Child-process segfaults are what the public lab produces, and they are worth a look in your error log, but they are an inference from the bug class and not a signature.
One more thing from the lab, with caveats. It includes a check that needs no race at all: for the trigger shape, the request’s logged Content-Length reads “-” on 2.4.70 and “0” on 2.4.71. It depends on the lab’s own log format and has not been shown against any distribution backport. Interesting, and not an attestation.
What stays with me
The ownership rule this bug broke was correct and documented, and the code followed it everywhere except one line. The lab’s author went back through the other writes to the stream’s header table in 2.4.71 and found them safe. Whether the same mistake exists elsewhere in httpd, wherever a worker thread writes into something a connection owns, is an audit nobody has published. We have not done it either.
For now, four numbers. One line of C. Almost four years in every release. Eight days between two independent reports. Two days between the public fix and the advisory.
You control none of them. What you control is whether your answer to “are we exposed?” comes from a version string or from the machine. Go read your Protocols lines.