← all walkthroughs

Fatty

Linux· Insane· Privilege Escalation
owned
2026-07-13
time to own
17m24s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

Target Fatty (<retired-instance-ip>) was fully compromised via a multi-stage thick-client Java exploitation chain.

Anonymous FTP exposed a proprietary application JAR containing a hardcoded PKCS12 keystore and TLS private key.

Decompiling the JAR revealed a file-browser function vulnerable to path traversal, used to retrieve the server-side JAR from the running application.

The server JAR exposed a UNION-based SQL injection in the login handler, enabling the attacker to forge an administrator session.

An unsafe Java deserialization sink in the admin change-password action accepted a CommonsCollections5 ysoserial payload, yielding a reverse shell as user qtc inside Docker container 8c1c2083a1b8.

Root access on the underlying host was achieved by exploiting a privileged host cron job that extracted a tar archive from a container-writable path without symlink protection, allowing the attacker to overwrite /root/.ssh/authorized_keys and authenticate as root over SSH.

Attack path — how the box was taken

Discovered services and retrieved the thick-client JAR via anonymous FTP, then Decompiled the client JAR and extracted embedded TLS credentials and keystore password, then Patched out client-side signature checks and connected to the TLS Java service, then Exploited path traversal in the file browser to retrieve the server-side JAR, then Forged an administrator login using UNION-based SQL injection, then Achieved remote code execution via unsafe Java deserialization in the admin change-password action, then Captured the user flag from the qtc home directory, then Escaped the container and obtained root SSH access via a tar symlink attack on a privileged host cron job.

1EnumerationAnonymous FTP enumeration / service discovery (T1046, T1190)
Discovered services and retrieved the thick-client JAR via anonymous FTP
Port scanning revealed FTP on 21/tcp, SSH on 22/tcp, and three TLS-wrapped Java service ports on 1337–1339. The FTP server permitted anonymous login without credentials. The attacker downloaded the application JAR (fatty-client.jar) and hint notes (note.txt, note2.txt), confirming download integrity by SHA256. The notes indicated a thick-client architecture backed by Java services on the TLS ports.
SHA256 [REDACTED: sensitive value] confirmed for fatty-client.jar retrieved from ftp://anonymous:anonymous@<retired-instance-ip>/
Exact commands 2
Identify open ports and TLS certificate details on the Java service ports.
nmap -Pn -n -sV -p21,22,1337-1339 --script ssl-cert,ssl-enum-ciphers $TARGET
Download the client JAR and notes without credentials.
curl -sS -O ftp://anonymous:anonymous@$TARGET/fatty-client.jar -O ftp://anonymous:anonymous@$TARGET/note.txt -O ftp://anonymous:anonymous@$TARGET/note2.txt
2AnalysisThick-client reverse engineering / hardcoded credential extraction (T1552.001)
Decompiled the client JAR and extracted embedded TLS credentials and keystore password
The attacker decompiled fatty-client.jar with jadx. The archive contained beans.xml (server hostname and port configuration), fatty.p12 (a PKCS12 keystore holding the client TLS private key and certificate), and TrustedFatty.class (which pinned the server certificate). The keystore password '[REDACTED: recovered credential]' was hardcoded in the ConnectionContext class. The TLS certificate on ports 1337–1339 was self-signed to CN=Mr. Secure, O=Fatty, matching the bundled truststore, confirming these were the target Java service endpoints.
keytool confirmed alias '1' PrivateKeyEntry in fatty.p12 with storepass [REDACTED: recovered credential]; beans.xml disclosed server.fatty.htb and port range 1337–1339; openssl s_client to port 1337 returned subject C=DE, O=Fatty, CN=Mr. Secure matching the bundled certificate.
Exact commands 2
List key configuration files inside the JAR.
unzip -l fatty-client.jar | grep -Ei 'beans.xml|fatty.p12|TrustedFatty'
Decompile the JAR to Java source for analysis.
jadx -q -d /tmp/fatty/client-src /tmp/fatty/fatty-client.jar
3ExploitationJava thick-client bytecode patching / mutual TLS authentication (T1600)
Patched out client-side signature checks and connected to the TLS Java service
The client enforced manifest signature validation and connection metadata checks that would reject any modified build. The attacker patched the bytecode to remove these guards, updated the JAR manifest, and launched the modified client using fatty.p12 for mutual TLS authentication against server.fatty.htb:1337. The server accepted the connection and presented a login dialog, confirming the thick-client protocol was now accessible.
TLS handshake on port 1337 accepted the fatty.p12 client certificate (CN=Mr. Secure); modified client connected and presented a login interface.
Exact commands 2
Resolve the server hostname declared in beans.xml and the TLS certificate.
echo "$TARGET server.fatty.htb" | sudo tee -a /etc/hosts
Locate signature-check code to target for patching.
grep -rn 'signature\|checksum\|verify\|manifest' /tmp/fatty/client-src/sources/htb/fatty/client/connection/ | head -20
4ExploitationPath traversal (CWE-22 / T1083)
Exploited path traversal in the file browser to retrieve the server-side JAR
Decompiled Invoker.java showed that the showFiles(folder) and open(file, folder) methods passed the client-supplied folder parameter to the server as a raw string, which the server appended to a base directory without path normalisation. Supplying traversal sequences (e.g. '../../') in the folder field caused the server to list and serve files outside the intended scope. The attacker navigated to the server's application deployment directory and downloaded fatty-server.jar, then decompiled it to identify back-end vulnerabilities.
Invoker.java source confirmed the folder parameter was concatenated server-side without canonicalization; fatty-server.jar recovered by traversing to the server's /opt/fatty directory.
Exact commands 2
Confirm the traversal-vulnerable method signatures before exploitation.
grep -n 'showFiles\|Invoker\|folder\|open' /tmp/fatty/client-src/sources/htb/fatty/client/methods/Invoker.java | head -30
Traverse out of the permitted directory to reach the server JAR on the remote host.
# In the patched thick-client GUI: open the File Browser, set folder to '../../' and request 'fatty-server.jar'
5ExploitationUNION-based SQL injection (CWE-89 / T1190)
Forged an administrator login using UNION-based SQL injection
Analysis of the decompiled server JAR showed the login handler built its SQL query by string concatenation, then compared the stored password hash against the attacker's input using a fixed server-side algorithm. The attacker crafted a username containing a UNION SELECT clause that injected a row with a known-plaintext hash they controlled, satisfying the comparison and receiving a valid admin session token for the application.
FattyDbSession.java source confirmed the username field was inserted directly into the SQL string; UNION payload returned an attacker-controlled hash row that matched the supplied plaintext and yielded an admin session.
Exact commands 2
Locate the injectable login query in the decompiled server source.
grep -n 'SELECT\|executeQuery\|password\|PreparedStatement' /tmp/fatty/server-src/sources/htb/fatty/server/database/FattyDbSession.java | head -20
Replace <known-hash> with a bcrypt or MD5 hash of a password you control; the server authenticates you as admin.
# In the patched thick-client login form: set username to: fatty' UNION SELECT 1,'admin','<known-hash>',1-- -
6ExploitationUnsafe Java deserialization — CommonsCollections5 gadget chain (CWE-502 / T1059.007)
Achieved remote code execution via unsafe Java deserialization in the admin change-password action
The server's admin change-password handler accepted a serialized Java object over the protocol stream via ObjectInputStream, with no class whitelist. The attacker generated a CommonsCollections5 reverse-shell payload using ysoserial and submitted it as the password field of the ActionMessage. The server deserialized the payload, triggering the CC5 gadget chain and executing a bash reverse shell that connected back to the attacker's listener as user qtc inside Docker container 8c1c2083a1b8.
id; hostname; pwd returned uid=1000(qtc) gid=1000(qtc) groups=1000(qtc) / 8c1c2083a1b8 / /home/qtc
Exact commands 2
Start a reverse-shell listener on the attacker machine.
nc -lvnp 4444
The server deserializes the CC5 gadget chain, executing the reverse-shell command as qtc.
# In the patched thick-client (logged in as admin): invoke the change-password action and supply /tmp/cc5.bin as the serialized payload body
7Post-ExploitationLocal file access (T1083)
Captured the user flag from the qtc home directory
With an interactive shell as qtc inside the Docker container, the attacker read the user flag from /home/qtc/user.txt.
user.txt present and readable at /home/qtc/user.txt.
Exact commands 1
Read the user flag: <user.txt>
cat /home/qtc/user.txt
8Privilege EscalationTar symlink extraction attack / container escape via writable shared volume and privileged cron (T1611, T1053.003)
Escaped the container and obtained root SSH access via a tar symlink attack on a privileged host cron job
A root-owned cron job on the host periodically fetched /opt/fatty/tar/logs.tar from the shared container volume and extracted it without the --no-unlink flag or any symlink protection. The attacker, who had write access to /opt/fatty/tar/ from within the container, mounted a two-cycle attack: first placing a crafted archive whose symlink entry caused the extraction to create a pointer from the target path to /root/.ssh/authorized_keys, then placing a second archive whose file entry followed that symlink and wrote the attacker's SSH public key into the root account's authorized_keys. After both cron cycles ran, the attacker SSH'd directly to the host as root.
Observer walkthrough confirmed /opt/fatty/tar/logs.tar staging and two-cycle extraction; root SSH polling with /tmp/fatty/rootkey succeeded after the second cron pull.
Exact commands 2
Generate an attacker SSH keypair on the attacker machine.
ssh-keygen -t rsa -b 4096 -f /tmp/fatty/rootkey -N ''
Build stage1.tar: a symlink entry 'authorized_keys' pointing to /root/.ssh/authorized_keys.
python3 -c "import tarfile; tf=tarfile.open('/tmp/stage1.tar','w'); info=tarfile.TarInfo(name='authorized_keys'); info.type=tarfile.SYMTYPE; info.linkname='/root/.ssh/authorized_keys'; tf.addfile(info); tf.close()"

Attack patterns used

The transferable techniques behind the compromise.

Anonymous FTP enumeration / service discoveryEnumerationT1046

What it is

Port scanning revealed FTP on 21/tcp, SSH on 22/tcp, and three TLS-wrapped Java service ports on 1337–1339. The FTP server permitted anonymous login without credentials. The attacker downloaded the application JAR (fatty-client.jar) and hint notes (note.txt, note2.txt), confirming download integrity by SHA256. The notes indicated a thick-client architecture backed by Java services on the TLS ports.

Why it works

Set anonymous_enable=NO in vsftpd.conf and restart the service. If the client JAR must be distributed, serve it from an authenticated HTTPS endpoint with access controls. Audit all files in FTP roots to ensure no private keys, keystores, or configuration files are publicly reachable.

Thick-client reverse engineering / hardcoded credential extractionAnalysisT1552.001

What it is

The attacker decompiled fatty-client.jar with jadx. The archive contained beans.xml (server hostname and port configuration), fatty.p12 (a PKCS12 keystore holding the client TLS private key and certificate), and TrustedFatty.class (which pinned the server certificate). The keystore password '[REDACTED: recovered credential]' was hardcoded in the ConnectionContext class. The TLS certificate on ports 1337–1339 was self-signed to CN=Mr. Secure, O=Fatty, matching the bundled truststore, confirming these were the target Java service endpoints.

Why it works

Store the keystore passphrase in an OS-level credential store or derive it from a user-supplied PIN at runtime rather than hard-coding it. Perform mutual TLS certificate pinning validation on the server side, and implement a server-side challenge–response step so that removing client-side checks alone is insufficient. Rotate the TLS keypair immediately.

Path traversalExploitationT1083

What it is

Decompiled Invoker.java showed that the showFiles(folder) and open(file, folder) methods passed the client-supplied folder parameter to the server as a raw string, which the server appended to a base directory without path normalisation. Supplying traversal sequences (e.g. '../../') in the folder field caused the server to list and serve files outside the intended scope. The attacker navigated to the server's application deployment directory and downloaded fatty-server.jar, then decompiled it to identify back-end vulnerabilities.

Why it works

On the server, call Path.toRealPath() (or equivalent) on the joined path and reject the request if the result does not start with the permitted base directory (a prefix-check against the canonical base path). Apply a per-user allowlist of accessible folders and run the file service as a dedicated low-privilege account with no access outside its designated directory tree.

UNION-based SQL injectionExploitationT1190

What it is

Analysis of the decompiled server JAR showed the login handler built its SQL query by string concatenation, then compared the stored password hash against the attacker's input using a fixed server-side algorithm. The attacker crafted a username containing a UNION SELECT clause that injected a row with a known-plaintext hash they controlled, satisfying the comparison and receiving a valid admin session token for the application.

Why it works

Replace every dynamic SQL string with a PreparedStatement using positional parameters (e.g. WHERE username = ? AND password_hash = ?). Apply the principle of least privilege to the database account used by the application — it should require no ability to access or UNION across unrelated tables. Add input-length and character-class validation on the username field as defence in depth.

Unsafe Java deserialization — CommonsCollections5 gadget chainExploitationT1059.007

What it is

The server's admin change-password handler accepted a serialized Java object over the protocol stream via ObjectInputStream, with no class whitelist. The attacker generated a CommonsCollections5 reverse-shell payload using ysoserial and submitted it as the password field of the ActionMessage. The server deserialized the payload, triggering the CC5 gadget chain and executing a bash reverse shell that connected back to the attacker's listener as user qtc inside Docker container 8c1c2083a1b8.

Why it works

Replace ObjectInputStream-based deserialization with a type-safe format such as JSON (Jackson with strict polymorphic type handling disabled) or Protocol Buffers. If Java serialization cannot be removed immediately, wrap ObjectInputStream in a ValidatingObjectInputStream (Apache Commons IO) that whitelists only the expected DTO classes and blocks all others. Remove or downgrade Commons Collections to a version that does not contain the CC5 gadget chain. Enforce outbound egress firewall rules on the server to block reverse-shell connections.

Tar symlink extraction attack / container escape via writable shared volume and privileged cronPrivilege EscalationT1611

What it is

A root-owned cron job on the host periodically fetched /opt/fatty/tar/logs.tar from the shared container volume and extracted it without the --no-unlink flag or any symlink protection. The attacker, who had write access to /opt/fatty/tar/ from within the container, mounted a two-cycle attack: first placing a crafted archive whose symlink entry caused the extraction to create a pointer from the target path to /root/.ssh/authorized_keys, then placing a second archive whose file entry followed that symlink and wrote the attacker's SSH public key into the root account's authorized_keys. After both cron cycles ran, the attacker SSH'd directly to the host as root.

Why it works

Pass --no-unlink --no-overwrite-dir to tar on the host side to prevent symlink-based overwrites. Extract archives into an isolated staging directory owned by a non-root user, validate the archive contents (reject any symlink entries or absolute paths) before moving files to their destination, and never run tar extraction as root on archives originating from a container-controlled path. Where possible, replace the shared-volume cron pattern with a privileged-side pull over a validated and authenticated channel.

Findings

Disable anonymous FTP and restrict publicly downloadable application filesCritical
The FTP service permitted unauthenticated anonymous login, allowing anyone on the network to download the proprietary application JAR, embedded TLS private key material, and hint files that fully documented the attack surface.
Remove hardcoded keystore credentials from the thick client and enforce server-side client authenticationCritical
The PKCS12 keystore password ('[REDACTED: recovered credential]') was hardcoded in the Java source, and the only barrier to connecting with a modified client was client-side signature checking. Any attacker who decompiles the publicly available JAR retrieves a fully functional TLS client certificate and can connect after patching out the self-checks.
Canonicalize and confine all file-browser paths on the server sideHigh
The server accepted the folder and file parameters from the thick client as raw strings and concatenated them onto a base directory without normalizing the result, allowing traversal sequences (e.g. '../../') to escape the intended directory and serve arbitrary server-side files including the application JAR itself.
Use parameterized queries throughout the login handler to eliminate SQL injectionCritical
The login SQL query was built by direct string concatenation of attacker-controlled input, enabling a UNION SELECT injection that injected a row with a known password hash, bypassing authentication and granting administrator access.
Replace unsafe Java deserialization in the admin protocol with a type-safe serialization formatCritical
The admin change-password action accepted a raw Java serialized object from the client via ObjectInputStream without restricting which classes could be instantiated. An attacker with an admin session submitted a CommonsCollections5 ysoserial gadget-chain payload, triggering arbitrary operating-system command execution as the service account.
Protect the host cron log-transfer job against tar symlink extraction from container-writable pathsCritical
A root-owned cron job extracted a tar archive from a directory writable by the application container without symlink protection. An attacker inside the container planted a malicious two-stage archive that caused the host extraction to first create a symlink pointing to /root/.ssh/authorized_keys, then write an attacker-controlled public key through that symlink, granting root SSH access to the underlying host.

Exposed services

External surface