CVE-2026-9082: Verified Repro With Script Download
CVE-2026-9082: Drupal core: unauthenticated SQL injection via JSON:API filter array keys
CVE-2026-9082 is verified against drupal/core · composer. Affected versions: 8.9.0 -> <10.4.10, 10.5.0 -> <10.5.10, 10.6.0 -> <10.6.9, 11.0.0 -> <11.1.10, 11.2.0 -> <11.2.12, 11.3.0 -> <11.3.10. Vulnerability class: RCE. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00133.
What Is CVE-2026-9082?
CVE-2026-9082 is a critical SQL injection vulnerability (CWE-89) in Drupal core's Entity Query SQL backend, exploitable by an unauthenticated attacker on sites backed by a PostgreSQL database. Pruva reproduced it (reproduction REPRO-2026-00133).
CVE-2026-9082 Severity & CVSS Score
CVE-2026-9082 is rated critical severity, with a CVSS base score of 9.8 out of 10.
Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.
Affected drupal/core Versions
drupal/core · composer versions 8.9.0 -> <10.4.10, 10.5.0 -> <10.5.10, 10.6.0 -> <10.6.9, 11.0.0 -> <11.1.10, 11.2.0 -> <11.2.12, 11.3.0 -> <11.3.10 are affected.
How to Reproduce CVE-2026-9082
pruva-verify REPRO-2026-00133 curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00133/artifacts/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-9082
Reproduced by Pruva's autonomous agents — 335 tool calls over 21 min. Full root-cause analysis and the complete transcript are below.
How the agent worked
Root Cause and Exploit Chain for CVE-2026-9082
Summary
CVE-2026-9082 is a highly critical SQL injection vulnerability in Drupal core's Entity Query SQL backend that affects only PostgreSQL-backed sites. The vulnerability exists because attacker-controlled array keys from entity query condition values are directly concatenated into named SQL placeholder identifiers in the PostgreSQL-specific Condition::translateCondition() method. An unauthenticated attacker can inject arbitrary characters (such as backticks, quotes, or SQL fragments) into the raw SQL text by providing malicious keys through JSON:API collection filter query parameters or the JSON login endpoint, resulting in SQL syntax errors or — with more sophisticated payloads — information disclosure and potential privilege escalation.
Impact
- Package/component affected: Drupal core, specifically the PostgreSQL entity query condition handler (
core/modules/pgsql/src/EntityQuery/Condition.php) and upstream condition compilation incore/lib/Drupal/Core/Entity/Query/Sql/Condition.phpandConditionAggregate.php - Affected versions: Drupal 8.9.0 through <10.4.10, 10.5.0 through <10.5.10, 10.6.0 through <10.6.9, 11.0.0 through <11.1.10, 11.2.0 through <11.2.12, 11.3.0 through <11.3.10
- Risk level: Highly critical (20/25 on Drupal's scale; CVSS 3.1 base 6.5)
- Consequences: Unauthenticated SQL injection on PostgreSQL sites. Can lead to information disclosure, privilege escalation, and in some configurations remote code execution. MySQL/MariaDB and SQLite backends are not affected.
Root Cause
The root cause is a trust boundary violation in core/modules/pgsql/src/EntityQuery/Condition.php. When processing an IN condition with case-insensitive comparison on PostgreSQL, the code iterates over $condition['value'] and appends each array key directly into the SQL placeholder name:
foreach ($condition['value'] as $key => $value) {
$where_id = $where_prefix . $key;
$condition['where'] .= 'LOWER(:' . $where_id . '),';
...
}
When JSON:API filter parameters or JSON login bodies decode JSON objects into associative PHP arrays, the attacker controls both the keys and values. A malicious key like ` (backtick) becomes part of the placeholder name: LOWER(:node_field_data_title). PDO then cannot bind :node_field_data_title`` because it was never added to the bound arguments list, producing SQLSTATE[HY093]: Invalid parameter number.
More advanced payloads can inject actual SQL fragments (e.g., using ) to close the LOWER() function call and || for PostgreSQL string concatenation), enabling boolean-blind SQL injection or error-based data exfiltration.
The fix is three array_values() calls that strip attacker-controlled keys before the vulnerable loop is reached:
core/lib/Drupal/Core/Entity/Query/Sql/Condition.phpcore/lib/Drupal/Core/Entity/Query/Sql/ConditionAggregate.phpcore/modules/pgsql/src/EntityQuery/Condition.php
Reproduction Steps
Reference script:
repro/reproduction_steps.shWhat the script does:
- Ensures PostgreSQL is running and creates a fresh
drupaldatabase with userdrupal/drupal - Prepares the Drupal core repository (clones if needed, installs Composer dependencies)
- Vulnerable phase: Checks out Drupal 11.3.9, installs a minimal site with PostgreSQL, enables the JSON:API module, creates a
pagecontent type and node, starts PHP's built-in server, and sends an anonymous GET request to/jsonapi/node/page?filter[t][condition][path]=title&filter[t][condition][value][%60]=x - Fixed phase: Repeats the same setup on Drupal 11.3.10 and sends the identical exploit request
- Ensures PostgreSQL is running and creates a fresh
Expected evidence:
- Vulnerable 11.3.9: The JSON:API endpoint returns HTTP 500 with a
SQLSTATE[HY093]: Invalid parameter numbererror. The response body contains the malformed SQL with the injected backtick inside the placeholder name (`:node_field_data_title``). - Fixed 11.3.10: The same request returns HTTP 200 with a normal JSON:API response body (
data: []), confirming the injection is blocked.
- Vulnerable 11.3.9: The JSON:API endpoint returns HTTP 500 with a
Evidence
Log files
logs/vulnerable_response.txt— HTTP response from the exploit against 11.3.9logs/fixed_response.txt— HTTP response from the same exploit against 11.3.10logs/repro_output.txt— Full console output from the first reproduction runlogs/repro_output_2.txt— Full console output from the second (idempotency) run
Key excerpts
Vulnerable 11.3.9 (logs/vulnerable_response.txt):
HTTP_CODE:500
{"errors":[{"title":"Internal Server Error","status":"500","detail":"SQLSTATE[HY093]: Invalid parameter number: parameter was not defined: SELECT ... WHERE ((LOWER(\"node_field_data\".\"title\") = (LOWER(:node_field_data_title`)))) ..."}]}
Fixed 11.3.10 (logs/fixed_response.txt):
HTTP_CODE:200
{"jsonapi":{"version":"1.1",...},"data":[],"links":{"self":{"href":"http://localhost:8080/jsonapi/node/page?..."}}}
Environment details
- PHP: 8.4.19 (CLI + built-in server)
- PostgreSQL: 16.13
- OS: Ubuntu 24.04
- Drupal test profile: Minimal install profile with JSON:API module enabled
Recommendations / Next Steps
- Immediate upgrade: All Drupal sites running on PostgreSQL should upgrade to the patched releases: 10.4.10, 10.5.10, 10.6.9, 11.1.10, 11.2.12, or 11.3.10 (whichever matches their branch).
- Verify backend: Site operators should confirm their database backend. Sites on MySQL, MariaDB, or SQLite are not affected by this specific vulnerability.
- WAF tuning: If immediate patching is not possible, consider blocking or sanitizing JSON:API filter query strings that contain special characters (
,',",),|,--) in filter value keys, as these are the characters that reach the vulnerable placeholder name construction. - Regression testing: After upgrading, verify JSON:API filter functionality still works correctly for normal use cases, since the fix reindexes condition value arrays with
array_values().
Additional Notes
- Idempotency confirmed:
repro/reproduction_steps.shwas executed twice consecutively with identical results (HTTP 500 + HY093 on 11.3.9, HTTP 200 on 11.3.10). - Limitations: The reproduction requires a real running Drupal site over HTTP with a PostgreSQL database. A minimal PHP harness or mock objects would not satisfy the ticket requirements and were explicitly rejected.
- Edge cases: The vulnerability only triggers on the PostgreSQL-specific case-insensitive
INcondition path. MySQL/MariaDB sites are unaffected because they use a different condition translation path that does not wrap comparisons inLOWER().
CVE-2026-9082 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.
Artifacts and Evidence for CVE-2026-9082
Scripts, logs, diffs, and output captured during the reproduction.
No artifacts available
How to Fix CVE-2026-9082
FAQ: CVE-2026-9082
How does the CVE-2026-9082 SQL injection attack work?
Which Drupal versions are affected by CVE-2026-9082?
How severe is CVE-2026-9082?
How can I reproduce CVE-2026-9082?
References for CVE-2026-9082
Authoritative sources for CVE-2026-9082 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.