https://bz.apache.org/bugzilla/show_bug.cgi?id=69994
Bug ID: 69994
Summary: ap_normalize_path bug
Product: Apache httpd-2
Version: 2.4.66
Hardware: PC
OS: Linux
Status: NEW
Severity: normal
Priority: P2
Component: Core
Assignee: [email protected]
Reporter: [email protected]
Target Milestone: ---
This was originally reported to the Apache security mailing list as a potential
potential path traversal vulnerability. After analysis, Joe Orton confirmed
that this is a valid bug, however it is not a vulnerability. As per his
request I am repositng this here.
Update / tl;dr;:
At ./server/request.c at line 268 we have:
ap_normalize_path(r->parsed_uri.path, normalize_flags);
This should be:
/* Fixed: */
if (!ap_normalize_path(r->parsed_uri.path,
AP_NORMALIZE_DROP_PARAMETERS |
AP_NORMALIZE_NOT_ABOVE_ROOT)) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO()
"invalid path after slash decoding: %s",
r->parsed_uri.path);
return HTTP_BAD_REQUEST;
}
Original Email:
__________
I was looking at the httpd code when I found a bug that **might** be indicative
of a supply chain attack wherein an attacker introduces code that seems
innocuous that eventually is changed into an attack vector.
To demonstrate:
$ curl -v --path-as-is 'http://codename-n184.com’
$ curl -v --path-as-is 'http://codename-n184.com/.%2F..%2fflag.txt’
This shows:
1. Gets the index.html from the document root
2. Using the AllowEncodedSlashes On directive, we can use %2F as a slash to
break out of our jail.
Specific results are appended at the end of the email.
So here’s where it gets interesting. As best I can tell, the key issue is this
bit of code in ./server/request.c at line 268:
ap_normalize_path(r->parsed_uri.path, normalize_flags);
The specific issue is that it should return an HTTP_BAD_REQUEST as in the first
normalization Or something like:
/* Fixed: */
if (!ap_normalize_path(r->parsed_uri.path,
AP_NORMALIZE_DROP_PARAMETERS |
AP_NORMALIZE_NOT_ABOVE_ROOT)) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO()
"invalid path after slash decoding: %s",
r->parsed_uri.path);
return HTTP_BAD_REQUEST;
}
The ignored return value at line 268 is not currently exploitable ONLY because
of a few other downstream stop gap checks that /could/ theoretically be removed
for various reasons, creating the issue we see here.
There is of course no way to prove whether this was just a mistake by whoever
committed it or an actual bad actor looking to create a future trojan / attack
vector. However because of the risk I am raising privately in a security
report before filing as a bug report.
The other interesting item is there is a default httpd.conf setting that does
this:
<Directory />
AllowOverride none
Require all denied
</Directory>
This is another protection layer that would seem to imply that if a bad actor
wanted to use this as an attack vector they’d need to somehow disable or change
to something like this:
<Directory "/usr/local/apache2/htdocs">
AllowOverride None
Require all granted
</Directory>
Anyways, here is a summary of potential supply chain attack surface. A change
to any of the following for whatever reason could create an exploit.
SUMMARY OF SUPPLY CHAIN ATTACK SURFACE
The ignored return value at request.c:268 is the pre-positioned weakness.
It is currently inert. Any ONE of the following changes activates it:
+-----------+-------------------+-----------------------------+-----------+
| Layer | File:Line | Change That Activates | Result |
+-----------+-------------------+-----------------------------+-----------+
| Layer 2 | util.c:577-579 | return 0 instead of ret=0 | Path |
| | | + continue | traversal |
+-----------+-------------------+-----------------------------+-----------+
| Layer 3 | core.c:4797-4799 | Remove APR_FILEPATH_ | Path |
| | | SECUREROOT flag | traversal |
+-----------+-------------------+-----------------------------+-----------+
| Layer 3 | core.c:4800-4803 | Don't return HTTP_FORBIDDEN | Path |
| | | on merge failure | traversal |
+-----------+-------------------+-----------------------------+-----------+
| Layer 4 | request.c:273-276 | Remove second walk_location | Location |
| | | _and_if() call | authz |
| | | | bypass |
+-----------+-------------------+-----------------------------+-----------+
Each of these changes would look innocent in isolation. Commit messages
like "optimize path normalization," "simplify translate_name," or "remove
redundant location walk" would not raise red flags in code review. But
combined with the already-ignored return at line 268, any one creates a
remotely exploitable vulnerability in the core HTTP request pipeline.
Whether the ignored return value was placed intentionally or is simply
sloppy code cannot be determined from the code alone. But regardless of
intent, the recommendation is the same: fix the ignored return value now,
because leaving a dormant disabled security check in the request pipeline
is exactly the pattern that supply chain attacks exploit.
PRIOR ART: The XZ Utils backdoor (CVE-2024-3094) used a similar staged
approach — individually innocent changes across multiple commits that
composed into a backdoor. The pattern of "disable a check now, remove
the compensating control later" is a known supply chain technique.
SVN ARCHAEOLOGY: To assess intent, examine the commit history for:
- Who introduced the ignored return at request.c:268 and when
- Whether the same author touched any of the three defense layers
- Whether there were any proposed patches that would have weakened
Layers 2, 3, or 4 that were later withdrawn or rejected
- The timing relationship between the ignored-return commit and any
changes to the compensating layers
RESULTS:
$ curl -v --path-as-is 'http://codename-n184.com’
* Host codename-n184.com:80 was resolved. * IPv6: (none) * IPv4: 174.136.99.221
* Trying 174.136.99.221:80... * Connected to codename-n184.com (174.136.99.221)
port 80 > GET / HTTP/1.1 > Host: codename-n184.com > User-Agent: curl/8.7.1 >
Accept: */* > * Request completely sent off < HTTP/1.1 200 OK < Date: Wed, 11
Mar 2026 22:54:15 GMT < Server: Apache/2.4.66 (Unix) < Last-Modified: Wed, 11
Mar 2026 22:54:08 GMT < ETag: "cd-64cc7837e010e" < Accept-Ranges: bytes <
Content-Length: 205 < Content-Type: text/html < <!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html>
<head> <title>Want to play a game?</title> </head> <body> <p>Can you capture
the flag?</p> </body> </html> * Connection #0 to host codename-n184.com left
intact
$ curl -v --path-as-is 'http://codename-n184.com/.%2F..%2fflag.txt’
* Host codename-n184.com:80 was resolved. * IPv6: (none) * IPv4: 174.136.99.221
* Trying 174.136.99.221:80... * Connected to codename-n184.com (174.136.99.221)
port 80 > GET /.%2F..%2fflag.txt HTTP/1.1 > Host: codename-n184.com >
User-Agent: curl/8.7.1 > Accept: */* > * Request completely sent off < HTTP/1.1
200 OK < Date: Wed, 11 Mar 2026 22:40:12 GMT < Server: Apache/2.4.66 (Unix) <
Last-Modified: Wed, 11 Mar 2026 22:39:42 GMT < ETag: "25-64cc74fdf2391" <
Accept-Ranges: bytes < Content-Length: 37 < Content-Type: text/plain < You have
the flag. Congratulations. * Connection #0 to host codename-n184.com left
intact
--
You are receiving this mail because:
You are the assignee for the bug.
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]