CVE-2026-25498

Crafting Chaos: RCE in Craft CMS via Yii2 Behavior Injection

Alon Barad
Alon Barad
Software Engineer

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:

  1. Injection: We inject a configuration for AttributeTypecastBehavior using the as rce key.
  2. 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).
  3. Payload: We set the attributeTypes map to link a harmless looking property (like uid) to a system command executor (like passthru or system).
  4. 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.

Fix Analysis (1)

Technical Appendix

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

Affected Systems

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

Affected Versions Detail

Product
Affected Versions
Fixed Version
Craft CMS
Pixel & Tonic
>= 4.0.0-RC1, < 4.16.184.16.18
Craft CMS
Pixel & Tonic
>= 5.0.0-RC1, < 5.8.225.8.22
AttributeDetail
CVE IDCVE-2026-25498
CVSS v4.08.6 (High)
CWECWE-470 (Unsafe Reflection)
Attack VectorNetwork (Authenticated)
ImpactRemote Code Execution (RCE)
Vulnerable Componentsrc/services/Fields.php
Exploit StatusPoC Available
CWE-470
Use of Externally-Controlled Input to Select Classes or Code ('Unsafe Reflection')

The application uses externally-controlled input to select the class or code to be executed, which can be manipulated to execute malicious code.

Vulnerability Timeline

Initial fix for related CVE-2025-68455
2025-12-05
Patch committed for CVE-2026-25498
2026-01-09
Advisory Published
2026-02-09

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.