The Apache path traversal (CVE-2021-41773)
In October 2021, Apache 2.4.49 shipped with a change to how it normalised request
paths. Within days, servers running it were having /etc/passwd read over the
web, and — where mod_cgi was loaded — having commands run on them. It was
exploited in the wild within hours of disclosure, by unauthenticated attackers,
with a one-line curl.
It is the clearest worked example on this site of two things: how thin the line is, and how much Apache’s defaults were doing to hold it.
What the bug was
Section titled “What the bug was”Apache is supposed to reject .. sequences that would climb above the document
root. CVE-2021-41773 was a flaw in that check: a .. encoded as %2e%2e
slipped through normalisation, so a request like
GET /cgi-bin/.%2e/.%2e/.%2e/.%2e/etc/passwdresolved, on the filesystem, to /etc/passwd.
The 2.4.50 fix was incomplete — it didn’t account for double encoding — so
CVE-2021-42013 followed days later with %%32%65 in place of .. Two CVEs,
one bug, one week.
With mod_cgi loaded, the same traversal
reached executables and ran them, turning file disclosure into remote code
execution:
POST /cgi-bin/.%2e/.%2e/bin/sh (with a command in the body)Why most servers were fine
Section titled “Why most servers were fine”This is the part worth internalising, because it’s the lesson rather than the trivia.
The exploit only reached files Apache was willing to serve. A default Apache config carries this:
<Directory /> Require all denied</Directory>That denies the entire filesystem by default and grants access back only for
specific document roots. So even with the traversal bug, a request for
/etc/passwd hit Require all denied and got a 403 — the bug climbed out of the
document root and immediately ran into a wall.
The servers that got hit were the ones that had replaced that default with
Require all granted — usually to make some unrelated 403 go away. They’d
removed the wall, so when the traversal bug arrived, there was nothing behind it.
That’s why keeping Require all denied is control
#10 on this site. It is not defence in depth against a hypothetical — it is the
specific thing that decided who got compromised in a real, widely-exploited event.
The shortest path from exposed to safe
Section titled “The shortest path from exposed to safe”- Patch. 2.4.49 and 2.4.50 are the vulnerable versions; 2.4.51 fixed both, and you should be far past that — 2.4.68 is current. If you’re on 2.4.49 or 2.4.50, this is not a hardening task, it’s an incident.
- Keep
Require all deniedon<Directory />— the control that contained it even unpatched. - Disable
mod_cgi/mod_cgid— the difference between file disclosure and code execution. If you serve PHP via FPM or proxy to an app, you don’t need CGI.
Steps 2 and 3 are the durable ones: they mean the next path bug — and there will be one — is contained the way this one was for the servers that kept their defaults.
Related
Section titled “Related”- Hardening Apache httpd — the full checklist, and patch first.
- Keep Require all denied — the control that contained this.