Skip to main content
← All articles
drupal

Drupal's rough summer: when the thing that stops SQL injection is the SQL injection

Drupal's rough summer: when the thing that stops SQL injection is the SQL injection

Drupal core had a bad early summer. Three security advisories in about a month, one of them the kind that gets a pre-announcement and a "clear your Wednesday evening" warning to every agency running a fleet of sites. I run this blog on MariaDB, which — spoiler — is the only reason I slept through the worst one. But the details are worth sitting with, because the scariest bug this summer was in the exact piece of Drupal whose entire job is to stop that class of bug.

SA-CORE-2026-004: the abstraction layer was the hole

On 18 May the security team did something they only do for genuinely alarming issues: they pre-announced a highly critical release two days out. It landed on Wednesday 20 May as SA-CORE-2026-004 (CVE-2026-9082), scoring 23 out of 25 on Drupal's risk scale and 9.8 on CVSS. A SQL injection. Exploitable by anonymous users. And by 22 May the advisory was updated to note exploit attempts in the wild — it's on CISA's Known Exploited list now.

Here's the part that stings. Drupal's whole pitch on database safety is that you use the query API and you don't have to think about injection — the abstraction layer sanitises everything. This was a flaw in that abstraction layer. The thing that exists to prevent SQL injection was the SQL injection.

The saving grace for most of us: it only affected sites on PostgreSQL. MySQL, MariaDB, SQLite — untouched. My blog runs MariaDB under Coolify, so this one sailed past me entirely. The independent write-ups (Tenable, Searchlight Cyber — note this is researcher analysis, not in the official advisory) trace it to the PostgreSQL-specific code that wraps comparisons in LOWER() to mimic MySQL's case-insensitivity, where attacker-controlled array keys ended up concatenated into the SQL. The fix was almost insultingly small. The lesson isn't: a database-portability shim, written to paper over a difference between engines, became an anonymous RCE-adjacent hole on exactly one engine.

Every abstraction you trust to make a problem "impossible" is also a single point where that problem can come back for everyone at once.

The JSON:API pair: object injection and a loaded gun

A month later, on 17 June, came two linked advisories I paid closer attention to, because I do expose JSON:API on this blog (it's how an AI editor account writes posts).

  • SA-CORE-2026-005 (CVE-2026-55803), Critical, 18/25 — a PHP object injection. An attacker with JSON:API write access to an entity that uses a field type storing serialized data could sneak a malicious payload past the protections added back in SA-CORE-2019-003. Yes: this is the same class of bug the team thought they'd closed seven years ago, resurfacing through a gap.
  • SA-CORE-2026-006 (CVE-2026-55804), Moderately Critical, 14/25 — a "gadget chain." On its own it does nothing. It's a sequence of method calls sitting in core that a deserialization bug could ride to remote code execution. A loaded gun with no trigger, waiting for a separate vulnerability to pull it.

What kept the blast radius small is pure defense-in-depth, and it's worth appreciating: JSON:API is read-only by default, and no field type shipped with core meets the "stores serialized data" condition. You needed a contrib or custom serialized field and granted write access to be exposed. The defaults did their job. But 006 is the honest gut-punch: core itself carries gadget chains, so any unserialize() of untrusted data anywhere in your stack isn't "might corrupt some data" — it's potentially the whole box.

A word on the two scoring systems

If you cross-referenced these against the NVD you'd have been confused: the JSON:API pair is "Critical / Moderately Critical" to Drupal but only 5.9 Medium on CVSS. That's not a contradiction, it's two philosophies. Drupal's 25-point score (derived from a NIST model, six factors from access complexity to how many sites are affected) and CVSS weight things differently — CVSS heavily discounts a bug that needs high privilege and complex conditions, which is exactly the JSON:API situation. Read both, trust neither blindly.

What I actually did

Updated, on the day, both times. That's the boring correct answer and there isn't a cleverer one. But the reflection stuck with me longer than the drush command. If you run Postgres, SA-004 is your reminder that "the framework handles it" is a promise, not a guarantee. And if you expose any write API over serialized fields — the AI-editor pattern is getting common — the JSON:API pair is your reminder that unserialize() on anything a user can influence is a bomb you're choosing to keep in the building. Core's defaults defused it this time. Your custom code might not have the same manners.

Links

BM
Blue Moose
The moose behind Blue Moose. Full-stack PHP developer — Drupal by day, Symfony by night, tests always.