Feb 9, 2026·5 min read·26 visits
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.
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 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.
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.
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:
element-indexes/get-elements.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.
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:
sessions table (if stored in DB) or the Admin password hash, crack it, and log in as Super Admin.Essentially, this is a "Game Over" vulnerability. The barrier to entry is low, and the ceiling for impact is the roof.
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| Product | Affected Versions | Fixed Version |
|---|---|---|
Craft CMS Pixel & Tonic | >= 4.0.0-RC1, <= 4.16.17 | 4.16.18 |
Craft CMS Pixel & Tonic | >= 5.0.0-RC1, <= 5.8.21 | 5.8.22 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-89 |
| CVSS v4.0 | 8.7 (High) |
| Attack Vector | Network (Authenticated) |
| Exploit Status | PoC Available |
| Vulnerability Type | SQL Injection via Mass Assignment |
| Affected Component | ElementIndexesController (get-elements) |
A vulnerability in the Slack and Mattermost platform adapters for NousResearch hermes-agent permits an unauthenticated remote attacker to execute arbitrary mass mentions. By leveraging prompt injection, an attacker can bypass output sanitization logic and trigger workspace-wide notification exhaustion.
CVE-2026-9306 is a critical unauthenticated Insecure Direct Object Reference (IDOR) vulnerability located in the QuantumNous new-api application, affecting versions up to and including 0.12.1. The flaw is caused by improper middleware ordering combined with a lack of object-level authorization checks. This allows remote, unauthenticated attackers to retrieve sensitive Midjourney images belonging to other users by supplying a valid task identifier.
The instagrapi library prior to version 2.6.9 contains an improper input validation vulnerability within its challenge handling mechanism. Maliciously crafted server responses can manipulate the client into forwarding session cookies and credentials to an external attacker-controlled domain.
GHSA-QQQM-5547-774X is a critical path traversal vulnerability in the FileBrowser Quantum application, specifically within the Go backend package. The vulnerability resides in the HTTP handler responsible for processing bulk file modifications via the public API. Unauthenticated attackers can exploit an order-of-operations flaw in the path sanitization logic to bypass intended directory restrictions. This allows adversaries to arbitrarily read, move, and overwrite files on the underlying filesystem by supplying specially crafted HTTP PATCH requests.
The qs query string parsing and serialization library for Node.js is vulnerable to a synchronous Denial of Service (DoS) attack. The vulnerability manifests as a process-terminating TypeError when processing arrays with null or undefined elements under specific configuration parameters.
The aiosend library prior to version 3.0.6 contains a pre-authentication Denial of Service (DoS) vulnerability in its webhook handling mechanism. The software processes and deserializes incoming JSON payloads before verifying the cryptographic signature, allowing unauthenticated attackers to exhaust server CPU and memory resources by sending large, complex payloads.