On 11 November 2026 the Apache HTTP Server project released version 2.4.71 with fixes for three CVEs. The one rated critical is CVE-2026-119618, titled “mod_http2: heap corruption and RCE in assign_headers”. This article summarizes what Apache and Wiz have published, gives the interim mitigation, and then uses the bug class as a prompt to audit shared state in application code. Our own examples are illustrations of the class, not reproductions of the Apache flaw.
What Apache says
Apache’s advisory describes a “Concurrent Execution using Shared Resource with Improper Synchronization” weakness (CWE-362) in mod_http2’s assign_headers() function that “can lead to RCE”. Per the advisory:
- Affected: Apache HTTP Server 2.4.55 through 2.4.70, and mod_h2 releases from GitHub 2.0.8 through 2.0.42.
- Fixed in: 2.4.71, released 2026-11-11.
- Credit: Anthropic’s OSS Scanner (reported 2026-10-26) and Anthony Weems and Simon Scannell of Google Cloud Vulnerability Research (reported 2026-11-03).
The CVE record’s severity is Apache’s textual rating “critical”. We did not find a numeric CVSS base score from the CNA, so we do not quote one here. As of 15 November, NVD shows only a CISA-ADP secondary score of 8.1 (attack complexity high), which is not directly comparable with Apache’s rating, and we found no evidence of the CVE in the CISA KEV catalog (catalog version 2026.11.13).
Wiz’s write-up adds that the flaw can corrupt heap memory, that it was exploitable by a remote, unauthenticated attacker if successful, and that as of its 11 November publication no exploit was available and no exploitation in the wild had been reported (Wiz’s statement is dated 11 November and may be out of date). Wiz also reports that, by its data, 64% of cloud environments have resources running a vulnerable version and 51% have publicly exposed instances, without accounting for configuration. Those are Wiz’s figures, not Apache’s. Apache’s advisory does not describe the exploitation mechanics, and neither do we.
What to do now
- Upgrade to Apache HTTP Server 2.4.71 or later.
- If you build mod_h2 separately from GitHub, take a release newer than 2.0.42 per the advisory’s affected range. Wiz’s table lists the patched version ambiguously, so confirm against the project’s release notes.
- If you cannot patch immediately, Wiz suggests disabling HTTP/2 so the vulnerable module path is not reachable:
# httpd.conf: serve HTTP/1.1 only (drops h2 and h2c)
Protocols http/1.1
Reload the server and confirm with curl -sI --http2 https://your-host/ that the response is not HTTP/2. Expect a performance and compatibility trade-off. Apache’s advisory itself does not list this workaround, so treat it as Wiz’s guidance and test it first.
Why this bug class keeps appearing
A race condition happens when two threads touch the same data and the outcome depends on timing. In memory-safe languages the usual result is corrupted logic: a double spend, a stale authorization decision. In C, as in a web server module, the same mistake can corrupt memory. The review question is identical: what state is shared across requests, and what protects it?
Python: check then act
# Vulnerable: two requests can both pass the check
balances = {"acct1": 100}
def withdraw(acct, amount):
if balances[acct] >= amount: # check
balances[acct] -= amount # act, not atomic with the check
return True
return False
import threading
_lock = threading.Lock()
def withdraw(acct, amount):
with _lock:
if balances[acct] >= amount:
balances[acct] -= amount
return True
return False
A process-wide lock only protects a single process. With multiple workers, enforce the invariant in the database, for example UPDATE accounts SET balance = balance - %s WHERE id = %s AND balance >= %s and check the affected row count.
Go: unsynchronized map
// Vulnerable: concurrent map writes can crash the process
var sessions = map[string]string{}
func handler(w http.ResponseWriter, r *http.Request) {
sessions[r.Header.Get("X-Id")] = r.RemoteAddr
}
var (
mu sync.Mutex
sessions = map[string]string{}
)
func handler(w http.ResponseWriter, r *http.Request) {
mu.Lock()
sessions[r.Header.Get("X-Id")] = r.RemoteAddr
mu.Unlock()
}
Run tests with go test -race ./.... The race detector reports unsynchronized accesses it observes during the run, so it only finds races your tests actually exercise.
Java: request data in a singleton field
// Vulnerable: servlets are shared across threads
public class ProfileServlet extends HttpServlet {
private String currentUser; // shared mutable state
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
currentUser = req.getParameter("user");
res.getWriter().println(lookup(currentUser)); // may read another request's value
}
}
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
String user = req.getParameter("user"); // local, per-request
res.getWriter().println(lookup(user));
}
Prefer per-request locals and immutable objects over locks wherever you can.
JavaScript: await breaks atomicity
// Vulnerable: another request can run between the check and the write
async function redeem(code) {
const coupon = await db.get(code);
if (!coupon.used) {
await db.markUsed(code);
await grantCredit();
}
}
Single-threaded does not mean race-free. Every await is a point where other requests run. Use an atomic conditional update (UPDATE ... WHERE used = false) and only grant credit if one row changed.
Review checklist (ours)
| Question | Look for |
|---|---|
| What is shared across requests? | Globals, singleton fields, caches, module-level dicts |
| Is check and act one atomic step? | Read, branch, then write with no lock or conditional update |
| Does the invariant hold across processes? | In-process locks guarding data that lives in a database |
| Do tests exercise concurrency? | Parallel tests, -race, stress loops |
| Is native code in the path? | Server modules and extensions, where races can become memory corruption |
For a deeper treatment of the web-application side, see race conditions and TOCTOU.