CVE-2026-23921: Verified Reproduction
CVE-2026-23921: Blind SQL injection in Zabbix API CApiService.php via the sortfield parameter allows low-privileged API users to exfiltrate database data and potentially compromise administrator accounts.
CVE-2026-23921 is verified against zabbix/zabbix · unknown. Affected versions: 7.0.0 through 7.0.21, 7.2.0 through 7.2.14, and 7.4.0 through 7.4.5. Vulnerability class: SQLi. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00360.
What Is CVE-2026-23921?
CVE-2026-23921 is a high-severity SQLi vulnerability affecting zabbix/zabbix 7.0.0 through 7.0.21, 7.2.0 through 7.2.14, and 7.4.0 through 7.4.5.. Pruva has independently reproduced it and publishes a verified, runnable proof-of-concept (reproduction REPRO-2026-00360).
CVE-2026-23921 Severity & CVSS Score
CVE-2026-23921 is rated high severity, with a CVSS base score of 8.7 out of 10.
High — serious impact or readily exploitable. Prioritize remediation.
Affected zabbix/zabbix Versions
zabbix/zabbix · unknown versions 7.0.0 through 7.0.21, 7.2.0 through 7.2.14, and 7.4.0 through 7.4.5. are affected.
How to Reproduce CVE-2026-23921
pruva-verify REPRO-2026-00360 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00360/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-23921
- reached the target end-to-end
- full exploit chain demonstrated
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
Authenticated JSON-RPC host.get params groupBy and sortfield containing the same MySQL conditional SLEEP expression
- POST /api_jsonrpc.php
- host.get
- CApiService::applyQuerySortOptions()
- ORDER BY
How the agent worked
Root Cause and Exploit Chain for CVE-2026-23921
CVE-2026-23921 is an authenticated blind SQL injection in the Zabbix JSON-RPC API. In Zabbix 7.0.21, legacy *.get methods preserve a caller-supplied groupBy option and CApiService::applyQuerySortOptions() uses that option as the allowlist for sortfield. Repeating an SQL expression in both fields makes validation approve attacker input, after which applyQuerySortField() places it into ORDER BY. This run confirmed the vulnerability through the real /api_jsonrpc.php endpoint as a newly created low-privileged User-role account. Matched predicates produced a repeatable timing oracle on 7.0.21, while 7.0.22 rejected the same requests.
- Affected component: Zabbix JSON-RPC API query generation in
ui/include/classes/api/CApiService.php, reached through legacy services such ashost.get. - Affected versions: 7.0.0–7.0.21, 7.2.0–7.2.14, and 7.4.0–7.4.5. Vendor-fixed releases are 7.0.22, 7.2.15, and 7.4.6.
- Risk level: High (vendor CVSS 4.0 score 8.7).
- Consequences: Any low-privileged account with API access can use true/false SQL predicates and database delay functions to infer arbitrary database values. The disclosed consequences include session identifier disclosure and subsequent administrator-account compromise.
Impact Parity
- Disclosed/claimed maximum impact: Authenticated remote blind SQL injection capable of arbitrary database-data exfiltration.
- Reproduced impact from this run: Authenticated low-privilege remote SQL injection through
/api_jsonrpc.php, demonstrated by three successful true predicates invokingSLEEP(2)and three false predicates returning immediately. Because onehost.getquery sorts two rows, each true probe delayed by approximately four seconds. The predicate operated onversion(), demonstrating database-expression evaluation and the binary timing channel used for extraction. - Parity:
full. - Not demonstrated: The run did not dump session identifiers or take over an administrator account; doing so is unnecessary once attacker-selected database predicates and the repeatable timing oracle are established.
Root Cause
The vulnerable 7.0.21 implementation computes:
$sort_columns = $group_by
? array_merge($options['groupBy'], $aggregate_sort_columns)
: $this->sortColumns;
The JSON-RPC caller controls options['groupBy']. An attacker therefore sends a legitimate field and an SQL expression in groupBy, then repeats the expression in sortfield. str_in_array() accepts the sort value because the application itself copied that value into sort_columns. applyQuerySortField() subsequently concatenates the accepted string into the SQL ORDER BY clause. countOutput: true and the legitimate hostid group field keep the rest of the generated aggregate query valid.
Zabbix 7.0.22 instead derives the allowed fields from the server-owned schema list:
$allowed_sort_fields = array_intersect($this->sortColumns, $options['groupBy']);
It also verifies groupBy is an array and each grouped field is a string. The first corrective commit in the release history is 0dced774b07bb542885787d82ddbdfe26ea782ee; subsequent DEV-4649 commits refactored the released validation. The tested release identities were source tags 7.0.21 (6542e21fe6adc8ab7bf903c2a9d1df80e80366cd) and 7.0.22 (584690ce104180ead8cd5d4d963a02b5e59bbeed). The primary runtime targets were official immutable Zabbix image digests recorded in runtime_manifest.json.
Reproduction Steps
- Run
bundle/repro/reproduction_steps.shfrom any working directory. Docker,curl,jq, Python 3,sha256sum, andtimeoutmust be available. - The script pulls immutable official image digests, starts isolated MySQL, Zabbix server, and Zabbix web/API containers for 7.0.21, and waits for a successful
apiinfo.versionJSON-RPC health check. - It logs in as bootstrap Admin only for setup, creates a normal User-role account with read-only access to the default host group, and confirms that account can perform a baseline
host.get. - Through that low-privileged account, it interleaves three false and three true
version()timing predicates in attacker-controlledgroupByandsortfieldvalues sent to/api_jsonrpc.php. - It destroys the vulnerable stack and repeats the exact procedure against an isolated 7.0.22 stack as the fixed negative control.
- It succeeds only if 7.0.21 has a median timing delta of at least 1.5 seconds and 7.0.22 either rejects every malicious request or has no timing oracle. It then writes
bundle/repro/runtime_manifest.jsonwith immutable artifact hashes.
Expected final output includes:
"vulnerable_timing_oracle": true
"fixed_negative_control": true
"confirmed": true
CONFIRMED: CVE-2026-23921 blind SQL injection reached through the real Zabbix API endpoint.
Evidence
- Final machine verdict:
bundle/repro/proof/verdict.json- Vulnerable timing delta:
3.973936498980038seconds. - Fixed timing delta:
-0.0018714029574766755seconds. vulnerable_timing_oracle=true,fixed_rejected_payload=true,confirmed=true.
- Vulnerable timing delta:
- Vulnerable endpoint transcript:
bundle/repro/proof/vulnerable/timing_results.json- False median:
0.04664472601143643seconds. - True median:
4.020581224991474seconds. - Every false and true request returned a valid JSON-RPC
result, showing that the vulnerable service executed rather than rejected the values.
- False median:
- Fixed negative control:
bundle/repro/proof/fixed/timing_results.json- All six requests returned JSON-RPC error
-32500:Sorting by field ... not allowed. - No sleep-dependent timing difference remained.
- All six requests returned JSON-RPC error
- Attacker requests:
bundle/repro/proof/vulnerable/false_probe_request.jsonandtrue_probe_request.jsoncontain the exact redacted request bodies. The session token is intentionally omitted. - Low-privilege precondition:
bundle/repro/proof/vulnerable/low_user_identity.jsonrecordstype: 1and role ID1;attempt_metadata.jsonrecords read-only permission over host group ID4.baseline_response.jsonconfirms the account could read a host. - Product and target identity:
product_version.txt,loaded_component.txt, andcontainer_identity.jsonin each role directory bind the run to real Zabbix 7.0.21 and 7.0.22 web products and the loadedCApiService.php. OCI labels identify the vulnerable image as 7.0.21, and the runtime manifest binds it to digestsha256:0282eba8bac999284672e3abaefbdd28b16026bae6a1673a7f560f35eb2c712d. - Runtime logs:
web_final.logandserver_final.login each proof directory show the actual HTTP JSON-RPC requests and product startup.bundle/logs/reproduction_steps.logremains diagnostic and is deliberately not hash-bound while the script is writing it. - Runtime manifest:
bundle/repro/runtime_manifest.jsonhasentrypoint_kind=endpoint,service_started=true,healthcheck_passed=true,target_path_reached=true, and hashes all 22 finalized proof artifacts.
Recommendations / Next Steps
- Upgrade to Zabbix 7.0.22, 7.2.15, 7.4.6, or a later supported release.
- Never derive identifier allowlists from request values. Intersect requested fields with a server-owned allowlist and map accepted logical names to known SQL identifiers.
- Enforce strict array/string schemas before all query-construction branches and reject undeclared options rather than preserving them through generic array merging.
- Add regression tests for duplicate
groupBy/sortfieldexpressions, scalar/object type confusion, aggregate aliases, and every legacy service sharingCApiService. - Until upgraded, restrict API access, revoke unnecessary API-capable accounts/tokens, and review web/database logs for
SLEEP,pg_sleep,CASE,IF,substring, or unusual comma-containing sort fields.
Additional Notes
- The final script revision completed successfully with immutable image digests. Two prior clean full runs also passed, with vulnerable median deltas of
3.9988and3.9967seconds and fixed controls showing no timing oracle. - Every stack uses a fresh MySQL database, private Docker network, unique container names, and teardown, so vulnerable and fixed observations cannot contaminate each other.
- Timing predicates are intentionally bounded to six probes per target and inspect only the first character of
version(); the script does not extract sensitive data. - Docker reported rootless operation without cgroups, but that did not affect service health, endpoint reachability, timing separation, isolation, or cleanup.
CVE-2026-23921 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
# Root Cause Analysis: CVE-2026-23921 ## Summary Zabbix API versions before the vendor fixes accept attacker-controlled `groupBy` values into the whitelist used to validate `sortfield`. A low-privileged authenticated API caller can repeat an SQL expression in both fields, causing it to be concatenated into the generated `ORDER BY` clause. This report will be finalized with current-run endpoint timing evidence after execution of `bundle/repro/reproduction_steps.sh`. ## Impact - **Affected component:** Zabbix JSON-RPC API, particularly `ui/include/classes/api/CApiService.php` and legacy API methods such as `host.get`. - **Affected versions:** 7.0.0 through 7.0.21, 7.2.0 through 7.2.14, and 7.4.0 through 7.4.5. - **Risk:** High. An authenticated low-privileged API user can perform blind SQL injection and infer arbitrary database values, potentially disclosing sessions and enabling administrator account compromise. ## Impact Parity - **Disclosed/claimed maximum impact:** Authenticated remote blind SQL injection with arbitrary database-data exfiltration. - **Reproduced impact from this run:** Pending current-run runtime validation. - **Parity:** none (pending execution). - **Not d… [truncated]
set -eu
printf '%s\n' '--- docker ---'
command -v docker || true
docker ps 2>&1 || true
printf '%s\n' '--- cache ---'
find /pruva/project-cache -maxdepth 2 -mindepth 1 -print 2>/dev/null | sort | head -100
printf '%s\n' '--- host ---'
uname -a
id--- docker --- /usr/bin/docker CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES --- cache --- /pruva/project-cache/.pruva /pruva/project-cache/.pruva/cache_manifest.json /pruva/project-cache/repo-mirrors --- host ---
Artifacts and Evidence for CVE-2026-23921
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-23921
FAQ: CVE-2026-23921
Is CVE-2026-23921 exploitable?
How severe is CVE-2026-23921?
What type of vulnerability is CVE-2026-23921?
Which versions of zabbix/zabbix are affected by CVE-2026-23921?
How can I reproduce CVE-2026-23921?
Is the CVE-2026-23921 reproduction verified?
References for CVE-2026-23921
Authoritative sources for CVE-2026-23921 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.