CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-25495
8.7

Craft CMS: The Art of SQL Injection via Mass Assignment

Alon Barad
Alon Barad
Software Engineer

Feb 9, 2026·5 min read·15 visits

PoC Available

Executive Summary (TL;DR)

Authenticated attackers can turn the Craft CMS Control Panel into a SQL playground. By sending a crafted JSON payload to the element index endpoint, you can inject raw SQL into the 'ORDER BY' clause. This happens because the code blindly applies user input to the database query builder object.

A high-severity SQL injection vulnerability in Craft CMS allows authenticated Control Panel users to execute arbitrary SQL commands. The flaw stems from a mass assignment vulnerability where user-supplied JSON keys are directly mapped to query builder methods, specifically bypassing sanitization mechanisms in the underlying Yii2 framework.

The Hook: Trust Issues in the Control Panel

We often treat the "Admin Panel" or "Control Panel" as a sanctuary—a place where only trusted individuals tread, and where input validation can arguably be a little looser. This assumption is, to put it mildly, dead wrong. CVE-2026-25495 is a perfect reminder that "Authenticated" does not mean "Trusted," and that even low-privileged content editors can be just as dangerous as external threat actors if the code permits it.

In the world of Craft CMS, the element-indexes/get-elements endpoint is the workhorse responsible for fetching lists of entries, users, or assets to display in the UI. It’s a dynamic, heavy-lifting component that needs to be flexible. Unfortunately, in its quest for flexibility, it committed one of the cardinal sins of secure coding: it allowed the user to define the structure of the database query directly via a JSON object.

This isn't just a simple case of a missing quote in a SQL string. It's a logic flaw involving "Mass Assignment"—a pattern where an application takes a pile of user input and blindly maps it to internal object properties. In this case, that internal object was a loaded gun pointed directly at the database.

The Flaw: Framework Magic Gone Wrong

The root cause lies in how Craft CMS leverages the Yii2 framework's "magic" configuration methods. Specifically, the code used Craft::configure($query, $criteria) to apply filters received from the frontend directly to the ElementQuery object. Think of Craft::configure as a function that says, "For every key in this array, find the corresponding property or setter method on the object and update it."

Here is the kicker: ElementQuery inherits from Yii2's Query Builder. This means it has public methods like orderBy(), where(), join(), and union(). When a user sends a POST request with a JSON body containing "criteria": {"orderBy": ...}, the application dutifully executes $query->orderBy(...) with the attacker's data.

But wait, doesn't Yii2 escape inputs? Usually, yes. However, Yii2 has a specific behavior for the orderBy method. If the string passed to it contains parentheses—for example, (id)—Yii2 assumes the developer is trying to use a raw SQL expression (like a database function) and disables escaping for that segment. This is a feature, not a bug, in the framework. But when you expose that feature directly to user input, it becomes a catastrophic vulnerability.

The Code: The Smoking Gun

Let's look at the crime scene in src/controllers/ElementIndexesController.php. The vulnerability is concise and elegant in its negligence.

The Vulnerable Code:

// Get the untrusted input from the request body
$criteria = Craft::$app->getRequest()->getBodyParam('criteria');
 
// ... contextual setup ...
 
// FATAL ERROR: Blindly apply input to the Query object
Craft::configure($query, Component::cleanseConfig($criteria));

The cleanseConfig method sounds safe, right? It's not. It mostly handles Project Config compatibility and doesn't sanitize against SQL injection vectors. It essentially allows the attacker to drive the ElementQuery object.

The Fix (Commit 96c60d775c644ff0a0276da52fe29e11d4cd38d2): The patch implements a blacklist approach (which is usually frowned upon, but necessary here given the architecture). They explicitly unset the dangerous methods from the input array before configuring the object.

// The "No-Fly" List
unset(
    $criteria['where'],
    $criteria['orderBy'], // The main culprit
    $criteria['select'],
    $criteria['join'],
    $criteria['union'],
    // ... and others
);
 
// Now it is safe(r) to configure
Craft::configure($query, Component::cleanseConfig($criteria));

By manually stripping out keys that correspond to structural query modifications, the developers ensure that user input can only influence safe properties (like limit or offset), effectively closing the open window.

The Exploit: Bypassing the Sanitizer

So, how do we weaponize this? We know we can call orderBy. We know that Yii2 will escape standard strings like id ASC. To inject SQL, we need to trigger the "Raw Expression" mode by using parentheses.

The Setup:

  1. Log in as a user with access to the Control Panel (e.g., a lowly content editor).
  2. Intercept a request to element-indexes/get-elements.
  3. Modify the JSON body.

The Payload:

{
  "action": "element-indexes/get-elements",
  "criteria": {
    "orderBy": "(elements.id), (SELECT SLEEP(5))"
  }
}

Why this works: When Yii2 receives (elements.id), (SELECT SLEEP(5)), it sees the parentheses and thinks, "Ah, the developer is doing something complex with DB functions. I better not touch this with my quoting logic." The resulting SQL generated by the backend looks something like this:

SELECT `elements`.* 
FROM `elements` 
... 
ORDER BY (elements.id), (SELECT SLEEP(5))

Since SLEEP(5) is a valid expression in MySQL, the database pauses for 5 seconds. This confirms we have Time-Based Blind SQL Injection. From here, an attacker can use standard techniques to extract the database version, current user, and eventually the users table to grab the admin's bcrypt hash.

The Impact: Why You Should Panic

While this requires authentication, don't let that lower your blood pressure. In many organizations, "Content Editor" or "Intern" accounts are shared, protected by weak passwords, or lack 2FA. Once an attacker compromises a low-level account, this vulnerability allows them to pivot to full system control.

With SQL Injection in Craft CMS, the attacker can:

  1. Dump the Database: Steal customer PII, order history, or proprietary data.
  2. Elevate Privileges: Extract the Admin session token from the sessions table (if stored in DB) or the Admin password hash, crack it, and log in as Super Admin.
  3. RCE (Remote Code Execution): Craft CMS allows Admins to edit templates. If an attacker elevates to Admin, they can often inject Twig code to execute arbitrary shell commands on the server.

Essentially, this is a "Game Over" vulnerability. The barrier to entry is low, and the ceiling for impact is the roof.

Official Patches

Craft CMSRelease notes for version 5.8.22 containing the security fix.

Fix Analysis (1)

Technical Appendix

CVSS Score
8.7/ 10
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N

Affected Systems

Craft CMS 4.x < 4.16.18Craft CMS 5.x < 5.8.22

Affected Versions Detail

Product
Affected Versions
Fixed Version
Craft CMS
Pixel & Tonic
>= 4.0.0-RC1, <= 4.16.174.16.18
Craft CMS
Pixel & Tonic
>= 5.0.0-RC1, <= 5.8.215.8.22
AttributeDetail
CWE IDCWE-89
CVSS v4.08.7 (High)
Attack VectorNetwork (Authenticated)
Exploit StatusPoC Available
Vulnerability TypeSQL Injection via Mass Assignment
Affected ComponentElementIndexesController (get-elements)

MITRE ATT&CK Mapping

T1190Exploit Public-Facing Application
Initial Access
T1059Command and Scripting Interpreter
Execution
CWE-89
Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')

Known Exploits & Detection

Manual AnalysisTime-based blind SQL injection via the 'orderBy' parameter in the criteria object.

Vulnerability Timeline

Fix committed to repository
2025-12-05
Public disclosure and CVE assignment
2026-02-09

References & Sources

  • [1]GitHub Advisory GHSA-2453-mppf-46cj
  • [2]Patch Commit Diff

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.