Skip to main content
Stéphane Quantin

Main navigation

  • Portfolio
  • About
  • Freelance Drupal
    • Audit de site Drupal
    • Drupal maintenance contract
  • Blog
  • CV
  • Contact
Languages
  • French
  • English

Breadcrumb

  1. Home
  2. Blog

SA-CORE-2026-004: Highly critical SQL Injection vulnerability in Drupal Core

By StephaneQ , Thu, 05/21/2026 - 16:52

On May 20, 2026, the Drupal Security Team published the security advisory SA-CORE-2026-004, rated Highly Critical with a Drupal security risk score of 20/25.

This vulnerability, referenced as CVE-2026-9082, is a SQL Injection issue in Drupal Core. More specifically, it affects Drupal’s database abstraction layer in a case related to Entity Queries executed against PostgreSQL databases.

Drupal had previously published a preventive announcement on May 18, 2026 through PSA-2026-05-18, encouraging site maintainers to reserve time for an immediate update once the security release became available.

Summary of the vulnerability

The vulnerability allows an attacker to send specially crafted requests that may lead to arbitrary SQL injection on Drupal sites using PostgreSQL.

According to Drupal, the vulnerability can be exploited by anonymous users, without prior authentication. Potential impacts include information disclosure, privilege escalation and, in some cases, remote code execution or chained attacks.

An important point: the SQL Injection vulnerability itself only affects sites using PostgreSQL. However, the published releases also include security updates for third-party dependencies, including Twig, Symfony and Composer. For this reason, all affected Drupal sites should be updated, even when they use MySQL or MariaDB.

Affected Drupal versions

The affected versions listed by Drupal are the following:

Drupal branch Affected versions Recommended fixed version
Drupal 11.3.x < 11.3.10 11.3.10
Drupal 11.2.x < 11.2.12 11.2.12
Drupal 11.1.x / 11.0.x < 11.1.10 11.1.10
Drupal 10.6.x < 10.6.9 10.6.9
Drupal 10.5.x < 10.5.10 10.5.10
Drupal 10.4.x and earlier < 10.4.10 10.4.10
Drupal 9.x End of life Manual best-effort patch
Drupal 8.9.x End of life Manual best-effort patch
Drupal 7 Not affected No specific action required for this vulnerability

Drupal lists the affected versions using the following constraints:

>= 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

Root cause: what happened in the code?

The root cause is located in the handling of certain SQL conditions generated by Entity Queries, more specifically in the PostgreSQL-specific implementation.

Drupal provides a Database Abstraction API whose role is to build portable and secure SQL queries across multiple database engines. In principle, dynamic values should be passed through placeholders instead of being concatenated directly into the query.

In the vulnerable case, the issue did not come from a SQL value being injected directly, but from the construction of the placeholder name itself.

The affected PostgreSQL code handled some arrays of values on case-insensitive fields. Schematically, the logic looked like this:

$where_prefix = str_replace('.', '_', $condition['real_field']);

foreach ($condition['value'] as $key => $value) {
  $where_id = $where_prefix . $key;
  $condition['where'] .= 'LOWER(:' . $where_id . '),';
  $condition['where_args'][':' . $where_id] = $value;
}

The values were correctly passed through $condition['where_args']. They were therefore parameterized. However, the array key, here $key, was used to dynamically build the placeholder name:

$where_id = $where_prefix . $key;

This placeholder was then injected into the SQL string:

$condition['where'] .= 'LOWER(:' . $where_id . '),';

In PHP, an array can be numerically indexed:

[
  'foo',
  'bar',
]

But it can also be associative:

[
  'key_1' => 'foo',
  'key_2' => 'bar',
]

The issue is that the code used array keys to generate SQL placeholder names. If these keys could be indirectly influenced by user input, they could end up inside the generated SQL fragment.

In other words, Drupal correctly protected the values, but potentially untrusted data could still influence the structure of the query through the placeholder name.

Why PostgreSQL only?

The vulnerability is related to a PostgreSQL-specific branch of the code, notably in the following class:

Drupal\Core\Entity\Query\Sql\pgsql\Condition

This logic is executed in a specific case: when the condition is applied to an array of values and the field is handled as case-insensitive.

In this case, Drupal builds a clause such as:

LOWER(field) OPERATOR (LOWER(:placeholder0), LOWER(:placeholder1), ...)

The placeholder names used in this specific construction could be influenced by the keys of the array passed to the condition.

This explains why the official advisory states that the SQL Injection vulnerability only affects sites using PostgreSQL.

The fix

The principle of the fix is simple: do not reuse associative array keys when generating SQL placeholder names.

The correction consists of forcing numeric reindexing of the values, for example with array_values(), before iterating over the array:

foreach (array_values($condition['value']) as $key => $value) {
  $where_id = $where_prefix . $key;
  $condition['where'] .= 'LOWER(:' . $where_id . '),';
  $condition['where_args'][':' . $where_id] = $value;
}

With this approach, even if the initial array is associative, its keys are ignored. After reindexing, the keys are necessarily numeric: 0, 1, 2, and so on.

The generated placeholder names are therefore fully controlled by Drupal again, for example:

:field0
:field1
:field2

The vulnerability therefore came from a subtle issue: untrusted data was not used as a SQL value, but as an element involved in building a SQL placeholder name.

A common wrong security assumption

This vulnerability illustrates a common mistake in SQL abstraction layers: assuming that using placeholders is enough to secure the entire query.

Placeholders protect the values bound to the query. They do not automatically protect the elements used to build the SQL structure itself: field names, operators, SQL fragments or, in this specific case, dynamically generated placeholder names.

The rule to remember is this: any data that can influence the structure of a SQL query must be considered sensitive, even if it is not directly a business value.

Included dependency updates

The releases published for Drupal 10 and Drupal 11 do not only fix this SQL Injection vulnerability.

They also include security updates for several dependencies:

  • Twig 3.26.0, which fixes several security vulnerabilities;
  • Symfony, updated to 6.4.40 for Drupal 10 and 7.4.12 for Drupal 11;
  • Composer 2.9.8, included as a hardening measure;
  • underscore.js 1.13.8 for Drupal 10.6.9.

Drupal also recommends checking which user roles can modify Twig templates, for example through Views or through contributed modules.

Recommended actions

The first action is to identify the exact Drupal Core version and the database engine used by the site.

drush status

It is also useful to check the installed version through Composer:

composer show drupal/core-recommended
composer show drupal/core

If the site uses PostgreSQL, the update should be considered a priority. If the site uses MySQL or MariaDB, this specific SQL Injection does not directly apply, but the update is still recommended because of the Twig, Symfony and other dependency security fixes.

Generic Composer command

To update Drupal Core and its related dependencies:

composer update "drupal/core-*" --with-all-dependencies

Example for Drupal 10.6.9

composer require drupal/core-recommended:10.6.9 drupal/core-composer-scaffold:10.6.9 drupal/core-project-message:10.6.9 --update-with-all-dependencies

Example for Drupal 11.3.10

composer require drupal/core-recommended:11.3.10 drupal/core-composer-scaffold:11.3.10 drupal/core-project-message:11.3.10 --update-with-all-dependencies

After the Composer update, apply the usual Drupal update process:

drush updatedb
drush cache:rebuild

Then check the site status:

drush status
composer audit

Recommended intervention plan

  1. Identify the current Drupal version: Drupal 10.4, 10.5, 10.6, 11.2, 11.3, and so on.
  2. Identify the database engine: PostgreSQL, MySQL, MariaDB or another engine.
  3. Back up the site: files, database, exported configuration and composer.lock.
  4. Update Drupal Core to the fixed version matching the branch currently in use.
  5. Run Drupal database updates with drush updatedb.
  6. Rebuild caches with drush cache:rebuild.
  7. Test critical user journeys: authentication, forms, back office, Views, search, APIs and e-commerce checkout where applicable.
  8. Review sensitive user roles, especially those that can modify Twig templates or Views.
  9. Monitor logs, including application, web server and database logs after deployment.

What about Drupal 8 and Drupal 9 sites?

Drupal 8 and Drupal 9 are end of life. No complete release is published for these branches.

Because of the severity of the vulnerability, Drupal nevertheless provides manual patches for Drupal 8.9 and Drupal 9.5. These patches are provided on a best-effort basis: they can help reduce the risk in the short term, but they are not a sustainable solution.

These older versions still contain other known vulnerabilities that will not be fixed by these patches. The recommendation is therefore to plan a migration to a supported branch as soon as possible, ideally Drupal 10.6 or Drupal 11.3 depending on the project context.

Should you update if the site does not use PostgreSQL?

Yes, the update is still recommended.

The SQL Injection described in SA-CORE-2026-004 only affects PostgreSQL. However, the published Drupal releases also include security fixes for Twig, Symfony and other dependencies.

A site using MySQL or MariaDB is therefore not exposed to this specific SQL Injection, but it may still be affected by vulnerabilities fixed in the bundled dependencies.

Conclusion

SA-CORE-2026-004 is an important vulnerability that should be addressed quickly, especially for Drupal sites using PostgreSQL.

The root cause is technically interesting: Drupal correctly parameterized SQL values, but still used array keys to dynamically build SQL placeholder names in certain PostgreSQL Entity Query cases.

The fix consists of no longer trusting the array keys and only keeping the values, by forcing numeric reindexing before generating the placeholders.

Beyond this specific vulnerability, this release is a reminder of the importance of having a clear Drupal maintenance process: monitoring security advisories, maintaining a staging environment, having reliable backups, running regression tests and being able to deploy security updates quickly.

Sources

  • Drupal core - Highly critical - SQL injection - SA-CORE-2026-004
  • Upcoming highly critical release on May 20, 2026 - PSA-2026-05-18
  • Release notes Drupal 10.6.9
  • Release notes Drupal 11.3.10
  • Twig 3.26.0 released

Tags

  • Drupal
  • Drupal 11
  • Drupal 10
  • Drupal 9

Social networks

  • Malt
  • codeur.com
  • 404Works
  • LinkedIn
  • Twitter
  • DoYouBuzz

Twitter

Tweets by @StephaneQ
RSS feed

Pied de page

  • Contact
  • Mentions légales
Powered by Drupal