Fatty
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.
Exact commands 2
nmap -Pn -n -sV -p21,22,1337-1339 --script ssl-cert,ssl-enum-ciphers $TARGETcurl -sS -O ftp://anonymous:anonymous@$TARGET/fatty-client.jar -O ftp://anonymous:anonymous@$TARGET/note.txt -O ftp://anonymous:anonymous@$TARGET/note2.txtExact commands 2
unzip -l fatty-client.jar | grep -Ei 'beans.xml|fatty.p12|TrustedFatty'jadx -q -d /tmp/fatty/client-src /tmp/fatty/fatty-client.jarExact commands 2
echo "$TARGET server.fatty.htb" | sudo tee -a /etc/hostsgrep -rn 'signature\|checksum\|verify\|manifest' /tmp/fatty/client-src/sources/htb/fatty/client/connection/ | head -20Exact commands 2
grep -n 'showFiles\|Invoker\|folder\|open' /tmp/fatty/client-src/sources/htb/fatty/client/methods/Invoker.java | head -30# In the patched thick-client GUI: open the File Browser, set folder to '../../' and request 'fatty-server.jar'Exact commands 2
grep -n 'SELECT\|executeQuery\|password\|PreparedStatement' /tmp/fatty/server-src/sources/htb/fatty/server/database/FattyDbSession.java | head -20# In the patched thick-client login form: set username to: fatty' UNION SELECT 1,'admin','<known-hash>',1-- -Exact commands 2
nc -lvnp 4444# In the patched thick-client (logged in as admin): invoke the change-password action and supply /tmp/cc5.bin as the serialized payload bodyExact commands 1
cat /home/qtc/user.txtExact commands 2
ssh-keygen -t rsa -b 4096 -f /tmp/fatty/rootkey -N ''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
Exposed services
| External surface | 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. |