Apache's Forbidden Dance: The Path Traversal Disaster of 2.4.49
Jan 7, 2026·6 min read
Executive Summary (TL;DR)
Apache 2.4.49 introduced a new path normalization function that forgot how URL encoding works. By replacing dots with `%2e`, attackers can walk out of the web root (`/var/www/html`) and into the server root (`/`). If `mod_cgi` is on, they can execute `/bin/sh` and take over the box. Patch immediately to 2.4.51 (skip 2.4.50, it was broken too).
A critical path traversal vulnerability in Apache HTTP Server 2.4.49 allows unauthenticated attackers to map URLs to files outside the expected document root. If CGI is enabled, this escalates to Remote Code Execution (RCE). The flaw stems from a botched path normalization logic change that failed to account for URL-encoded characters.
The Hook: A "Minor" Refactor Gone Wrong
It was supposed to be a routine update. Apache HTTP Server 2.4.49 dropped in September 2021, promising some performance tweaks and code cleanups. Among these changes was a refactor of server/util.c, specifically the ap_normalize_path function. The goal? To streamline how the server handles URL paths, ensuring that messy inputs like /./ and /foo/../bar get tidied up before the server decides which file to serve.
But here is the thing about parsing logic in C: it is a minefield. The developers decided to implement their own logic to strip out path traversal sequences (the dreaded ..). In doing so, they committed one of the cardinal sins of input validation: they checked for the dangerous pattern before fully decoding the input, or rather, they performed a check that was too literal for its own good.
For a brief window, the internet's most popular web server was essentially checking IDs at the front door while leaving the side window unlocked, unbarred, and with a neon sign pointing to /etc/passwd. It didn't take long for the security community—and the ransomware operators—to notice.
The Flaw: Literal Checks in an Encoded World
The vulnerability lies in how ap_normalize_path handled the removal of dot-segments. The server needs to ensure that a user cannot request http://target/../../etc/passwd. To do this, the code iterates through the path string, looking for dots.
In version 2.4.49, the logic checked for a dot specifically by looking for the ASCII character . (0x2E). If it found a dot followed by another dot, it triggered the traversal sanitation logic. The problem? The web speaks URL-encoding. A dot isn't just .; it is also %2e.
When an attacker sends a request containing %2e, the normalization function looks at the string. It sees the % symbol, not a dot. It says, "Hey, that's not a dot, carry on." The check fails, the sanitization is skipped, and the path remains valid in the eyes of the normalizer. However, later down the pipeline, the operating system or the subsequent file handler decodes that %2e back into a dot. Suddenly, /icons/.%2e/ becomes /icons/../, and the attacker has just stepped out of the sandbox.
The Code: The Smoking Gun
Let's look at the C code that caused the apocalypse. In server/util.c, the traversal check looked something like this:
/* Inside ap_normalize_path */
if (path[l + 1] == '.' && IS_SLASH_OR_NUL(path[l + 2])) {
/* Logic to collapse the segment */
}See the issue? It is explicitly comparing path[l+1] to '.'. It assumes the path is already canonicalized or that dangerous characters are only represented in their literal form.
When the patch was hurriedly applied (initially for 2.4.50), they added logic to explicitly check for the encoded version. Here is the diff that attempted to fix it:
/* The incomplete fix in 2.4.50 */
if ((path[n] == '.' || (decode_unreserved
&& path[n] == '%'
&& path[++n] == '2'
&& (path[++n] == 'e'
|| path[n] == 'E')))
&& IS_SLASH_OR_NUL(path[n + 1])) {They essentially said, "Okay, if it's a dot OR if it's a percent sign followed by '2' and 'e', then treat it as a dot." Spoiler alert: Attackers immediately bypassed this fix too by using double URL encoding (%%32%65), leading to CVE-2021-42013. But for 2.4.49, the code was completely blind to encoding.
The Exploit: From LFI to RCE
Exploiting this is embarrassingly simple. No heap feng shui, no race conditions, just a malformed string. You need to find a directory that is explicitly "Granted" in the Apache config. Usually, the DocumentRoot is protected, but alias directories like /icons/ or /cgi-bin/ often have looser permissions.
Step 1: The Sanity Check (LFI)
Attackers start by trying to read the password file. The payload uses %2e to mask the second dot in the traversal sequence:
curl -v 'http://target.com/cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd'If the server returns root:x:0:0:..., you have a confirmed Path Traversal.
Step 2: Remote Code Execution (RCE)
The real danger arises if mod_cgi is enabled. If the server allows script execution in the directory we are traversing from (like /cgi-bin/), we can traverse out of the CGI bin and into the system binary folder (/bin). We then ask Apache to execute /bin/sh as if it were a CGI script.
curl -v -X POST -d "echo Content-Type: text/plain; echo; id" \
'http://target.com/cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/bin/sh'Apache happily executes sh, pipes our POST body into stdin, and returns the output of id. Congratulations, you are now the daemon user (or www-data).
The Impact: Why We Should Panic
This vulnerability scored a functional 9.8 CVSS because it is trivial to exploit and provides high impact. The EPSS score sits at nearly 95%, meaning if you run Apache 2.4.49 exposed to the internet, you will get hit.
The impact is twofold. First, Data Exfiltration. Configuration files, source code, and sensitive system files are readable. Second, and more importantly, Remote Code Execution. With RCE, the attacker can install backdoors, join the server to a botnet, or pivot deeper into the internal network.
What makes this particularly nasty is that it bypasses standard ACLs defined in httpd.conf because the path normalization happens before the authorization logic fully processes the "real" path. It is a logic error that undermines the entire security architecture of the web server.
The Fix: Stop the Bleeding
If you are running Apache 2.4.49, you are actively being hunted. The fix in 2.4.50 was incomplete (it missed double-encoding), so you must upgrade to 2.4.51 or later immediately.
If you cannot upgrade (why?), you can use mod_rewrite rules to block the encoded characters, but that is a band-aid on a bullet wound. The robust fix is the binary patch.
Additionally, this highlights the importance of the "Deny All" default configuration. Your Apache config should always start with this:
<Directory />
AllowOverride none
Require all denied
</Directory>This ensures that even if a traversal occurs, the server refuses to serve files from the root directory unless explicitly allowed elsewhere. It is the digital equivalent of locking the internal doors, not just the front one.
Official Patches
Fix Analysis (1)
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:HAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
Apache HTTP Server Apache Software Foundation | = 2.4.49 | 2.4.51 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-22 |
| Attack Vector | Network |
| CVSS | 9.8 (Critical) |
| EPSS Score | 94.38% |
| Impact | RCE & Information Disclosure |
| Exploit Status | Active / Weaponized |
| KEV Listed | Yes |
MITRE ATT&CK Mapping
The software uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the software does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.