Crafting Chaos: RCE in Craft CMS via Yii2 Behavior Injection
Feb 9, 2026·6 min read·9 visits
Executive Summary (TL;DR)
Authenticated RCE in Craft CMS (v4/v5). The `fieldLayout` parameter allows attackers to inject Yii2 Behaviors into object creation. By attaching `AttributeTypecastBehavior`, an attacker can trigger shell commands during server-side validation. Patch immediately to v5.8.22 or v4.16.18.
A high-severity Remote Code Execution (RCE) vulnerability exists in Craft CMS versions 4 and 5, specifically within the `assembleLayoutFromPost` method. The flaw stems from the unsafe usage of the Yii2 framework's dependency injection container, allowing authenticated administrators to inject malicious configuration arrays. By leveraging Yii2 'Behaviors,' attackers can achieve arbitrary code execution during object instantiation, effectively turning the CMS against its host server.
The Hook: Magic Methods, Magic Misery
If you've ever worked with the Yii2 framework (the engine purring underneath Craft CMS), you know it loves 'magic.' It has magic properties, magic events, and a dependency injection (DI) container that feels like wizardry. You pass it an array, and poof, it hands you a fully instantiated object with properties set, event listeners attached, and behaviors mixed in.
But here's the thing about magic: if you let a stranger cast the spells, you're going to have a bad time. CVE-2026-25498 is the result of exactly that—letting the user (even an authenticated one) dictate the configuration array passed to Craft::createObject().
This isn't a simple buffer overflow or a logic error. This is Object Injection via configuration. It's the PHP equivalent of handing someone a loaded gun and asking them to hold it while you tie your shoes. The specific vulnerability lies in how Craft CMS handles field layouts. When an admin saves a layout, the application takes a JSON blob from the request and feeds it directly into the object factory. If you know the right incantations (read: Yii2 internal classes), you can turn that JSON blob into a remote shell.
The Flaw: Trusting the 'Admin' Badge
The vulnerability lives in src/services/Fields.php, inside the assembleLayoutFromPost function. The purpose of this function is mundane: it reconstructs a Field Layout object based on data sent from the browser. It's supposed to take a JSON structure defining where fields go on the screen and hydrate a PHP object representing that layout.
Here is where the developer's optimism clashed with reality. The code assumed that because the user is an authenticated administrator, they wouldn't try to blow up the server.
Technically, the flaw is CWE-470: Use of Externally-Controlled Input to Select Classes or Code. The application accepts a raw JSON string from the fieldLayout POST parameter, decodes it into a PHP array, and passes that array straight into createLayout, which eventually calls Craft::createObject($config).
In Yii2, if a configuration array contains a key starting with as , the framework treats it as a Behavior attachment. Behaviors are mixins that can inject methods and properties into a class at runtime. If you control the behavior, you control the object's lifecycle. And if you control the lifecycle, you can hook into events like EVENT_AFTER_VALIDATE to execute code.
The Code: A Game of 'Spot the Difference'
Let's look at the smoking gun. This is the code running in src/services/Fields.php before the patch. Note the absolute lack of sanitization:
public function assembleLayoutFromPost(?string $namespace = null): FieldLayout
{
$paramPrefix = $namespace ? rtrim($namespace, '.') . '.' : '';
// VULNERABLE: Takes raw user input, decodes it, and creates an object.
// There is no check on what keys exist in this JSON.
$config = Json::decode(Craft::$app->getRequest()->getBodyParam($paramPrefix . 'fieldLayout'));
return $this->createLayout($config);
}The fix is painfully simple but highlights the root cause perfectly. The developers wrapped the input in ComponentHelper::cleanseConfig(). This helper method was actually introduced to fix a previous similar vulnerability (CVE-2025-68455). It seems they missed this specific spot during the first sweep—a classic case of 'Whack-a-Mole' security.
public function assembleLayoutFromPost(?string $namespace = null): FieldLayout
{
$paramPrefix = $namespace ? rtrim($namespace, '.') . '.' : '';
// PATCHED: The config is now scrubbed for dangerous keys before use.
$config = ComponentHelper::cleanseConfig(
Json::decode(Craft::$app->getRequest()->getBodyParam($paramPrefix . 'fieldLayout'))
);
return $this->createLayout($config);
}What does cleanseConfig do? It recursively iterates through the array and nukes any key that starts with as (behaviors) or on (event handlers). It effectively neuters the magic, forcing the array to be treated as just data, not instructions.
The Exploit: Building the Gadget Chain
So, we can attach a behavior. Now what? We need a "gadget"—a class already present in the codebase that does something dangerous when we poke it. Enter yii\behaviors\AttributeTypecastBehavior.
This class is designed to automatically convert model attributes to specific types (e.g., integer, string). However, it allows you to define a callback for the conversion. If we tell it to convert the uid attribute using the passthru function, it will execute passthru($uid).
Here is the attack chain:
- Injection: We inject a configuration for
AttributeTypecastBehaviorusing theas rcekey. - Trigger: We configure the behavior to run on
typecastAfterValidate. This means the code executes automatically when the system tries to validate the layout object (which happens immediately upon save). - Payload: We set the
attributeTypesmap to link a harmless looking property (likeuid) to a system command executor (likepassthruorsystem). - Execution: We provide the command we want to run as the value of
uid.
The Payload:
{
"as rce": {
"class": "yii\\behaviors\\AttributeTypecastBehavior",
"attributeTypes": {
"uid": "passthru"
},
"typecastAfterValidate": true
},
"uid": "id; uname -a"
}When the server receives this, it creates the object, attaches the behavior, sets uid to id; uname -a, validates the object, triggers the event, and executes the shell command. The server response might hang or return the output, depending on how passthru interacts with the output buffer, but the damage is done.
The Impact: From Admin to God Mode
You might be thinking, "But you need to be an Admin to do this!" You are correct. This is an authenticated vulnerability (CVSS PR:H). However, in the real world, this is a catastrophic escalation.
Compromising a CMS admin account is often trivial. Admins reuse passwords. Admins get phished. Admins leave sessions open. In a standard scenario, an attacker with admin access can deface a site or steal customer data. With this vulnerability, they get root (or at least www-data) on the web server.
Once code execution is achieved, the attacker can:
- Install a persistent webshell.
- Pivot to the internal network.
- Exfiltrate environment variables (AWS keys, database credentials).
- Deploy ransomware.
This turns a "website problem" into an "infrastructure problem." It completely invalidates the security model of the application.
The Fix: Scrubbing the Input
If you are running Craft CMS 4 or 5, you need to update immediately. The fix is included in versions 5.8.22 and 4.16.18.
If you cannot update right now (perhaps you're locked in a change freeze or your dev team is on vacation), you can try to mitigate this at the WAF level. Block any POST request to /admin/* that contains the JSON substring "as followed by a generic alphanumeric string. However, WAF rules for JSON are notoriously brittle and prone to bypasses (e.g., using unicode escapes).
The real fix is the patch. The cleanseConfig method is the only reliable way to ensure that user input—even from trusted admins—cannot instantiate dangerous framework internals. This serves as a reminder to all PHP developers: Never pass user-controlled arrays to unserialize or Dependency Injection containers without strict allow-listing.
Official Patches
Fix Analysis (1)
Technical Appendix
CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:NAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
Craft CMS Pixel & Tonic | >= 4.0.0-RC1, < 4.16.18 | 4.16.18 |
Craft CMS Pixel & Tonic | >= 5.0.0-RC1, < 5.8.22 | 5.8.22 |
| Attribute | Detail |
|---|---|
| CVE ID | CVE-2026-25498 |
| CVSS v4.0 | 8.6 (High) |
| CWE | CWE-470 (Unsafe Reflection) |
| Attack Vector | Network (Authenticated) |
| Impact | Remote Code Execution (RCE) |
| Vulnerable Component | src/services/Fields.php |
| Exploit Status | PoC Available |
MITRE ATT&CK Mapping
The application uses externally-controlled input to select the class or code to be executed, which can be manipulated to execute malicious code.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.