Feb 26, 2026·5 min read·47 visits
A critical RCE in Yii 2 (< 2.0.52) caused by a regression in a previous security fix. Attackers bypass class validation by using the `__class` key instead of `class`, allowing arbitrary object instantiation and code execution. Actively exploited.
History has a funny way of repeating itself, especially in PHP frameworks. CVE-2024-58136 is not just a vulnerability; it is a masterclass in how 'patching' can sometimes just mean 'moving the target.' In July 2024, the Yii team patched a critical RCE (CVE-2024-4990) involving behavior attachments. They added a check. It looked solid. But they forgot one tiny detail about their own Dependency Injection container: it speaks two languages. By simply using `__class` instead of `class`, attackers could bypass the check entirely, leading to active exploitation in the wild, particularly devastating for Craft CMS installations.
In the world of secure coding, the most dangerous moment is often immediately after a patch is released. Why? Because developers relax, and hackers start diffing. In July 2024, Yii Framework released version 2.0.51 to fix a nasty Remote Code Execution bug (CVE-2024-4990). The issue was simple: you could attach 'behaviors' (mixins) to components via user input, and if you attached a malicious class, you got RCE.
The fix seemed logical: before attaching a behavior, check if the requested class is actually a subclass of yii\base\Behavior. It’s like checking ID at the door of a club. If you aren't on the list, you don't get in.
But here is where the plot twists. The Yii Dependency Injection (DI) container is a polyglot. It accepts definitions in multiple formats. The developers checked one format (class), but the DI container happily prioritizes another (__class). It’s like the bouncer checking your ID, but the bartender serving anyone who whispers a secret password. This oversight turned a 'security fix' into a blueprint for bypass.
To understand this vulnerability, you have to understand how Yii creates objects. The Yii::createObject($config) method is the heart of the framework. It takes a configuration array and spits out an object. Usually, you specify the type of object using the class key.
However, for historical reasons or advanced configuration merging, Yii also supports __class. When Yii::createObject receives an array, it looks for __class first. If found, it uses that class name. If not, it falls back to class.
The vulnerability lies in yii\base\Component::__set. This magic method handles property assignment. When you try to assign a configuration to a property like as myBehavior, Yii attempts to attach it. In version 2.0.51, the code validated config['class']. It ensured config['class'] was a safe Behavior subclass.
But then it passed the entire config array to Yii::createObject.
So, if an attacker sends a payload with both keys:
class: "SafeBehavior" (The bouncer checks this. It's safe. Pass.)__class: "MaliciousGadget" (The factory uses this. It's deadly. Boom.)The validation logic and the execution logic were looking at different parts of the same data structure. This is a classic "Time-of-Check to Time-of-Use" (TOCTOU) style logic error, but purely with array keys.
Let's look at the diff. It is painfully simple, which makes it all the more tragic.
Here is the vulnerable code in 2.0.51:
// yii/base/Component.php
} elseif (isset($value['class']) && is_subclass_of($value['class'], Behavior::class, true)) {
// The check passes if 'class' is valid.
// But createObject might use '__class' if it exists in $value!
$this->attachBehavior($name, Yii::createObject($value));
}And here is the fix in 2.0.52:
// yii/base/Component.php
} elseif (
(isset($value['class']) && is_subclass_of($value['class'], Behavior::class)) ||
// NOW we check __class too
(isset($value['__class']) && is_subclass_of($value['__class'], Behavior::class))
) {
$this->attachBehavior($name, Yii::createObject($value));
}The fix is literally just acknowledging the existence of the shadow key. If you are a developer, write this on a sticky note: "Validate the data you use, not the data you expect."
So how do we turn this into a shell? We need a target application that allows user input to flow into a component configuration. Craft CMS was the unlucky winner here, exposing this via image transform settings.
The attack requires a "Gadget"—a PHP class that does something dangerous when instantiated or destructed. A classic favorite in the PHP ecosystem is GuzzleHttp\Psr7\FnStream. This class has a destructor that calls a user-defined function.
Here is what a weaponized JSON payload looks like:
{
"as trigger": {
"class": "yii\\behaviors\\AttributeBehavior",
"__class": "GuzzleHttp\\Psr7\\FnStream",
"__construct()": [[]],
"_fn_close": "system",
"methods": {
"close": "id"
}
}
}The Breakdown:
as trigger: Tells Yii to attach a behavior named "trigger".class: Points to AttributeBehavior. This satisfies the security check in 2.0.51.__class: Points to FnStream. This is what Yii::createObject actually builds._fn_close: The property on FnStream that holds the function name to call on destruction (system).close: The argument passed to the function.When the request ends, the FnStream object is destroyed, system('id') is executed, and the attacker wins.
This isn't theoretical. This is in the CISA Known Exploited Vulnerabilities (KEV) catalog. Attackers are actively scanning the internet for unpatched Yii applications, specifically Craft CMS.
Once RCE is achieved, the game is over. In the wild, we've seen attackers using this to:
db.php or .env files to get database credentials.Because this runs as the web server user (usually www-data), the attacker has full read/write access to the webroot. In modern containerized environments, they might just steal environment variables and move on. In legacy VPS hosting, they own the box.
If you are running Yii 2, check your composer.json immediately. If your version is < 2.0.52, you are vulnerable.
Remediation Steps:
composer update yiisoft/yii2. Ensure you land on at least 2.0.52.vendor/yiisoft/yii2/base/Component.php file manually if you cannot update immediately. Look for the __class check in __set.If you cannot patch (why?), you can try to mitigate this at the WAF level by blocking requests containing __class in the body, but this is risky and prone to false positives or bypasses (e.g., JSON encoding variations). Patching is the only real fix.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Yii Framework YiiSoft | < 2.0.52 | 2.0.52 |
Craft CMS Pixel & Tonic | Various (depends on Yii version) | Latest Stable |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-502 (Deserialization of Untrusted Data) |
| CVSS v3.1 | 9.8 (Critical) |
| Attack Vector | Network (Remote) |
| Impact | Remote Code Execution (RCE) |
| Exploit Status | Active / Weaponized |
| KEV Status | Listed (2025-05-02) |
The application deserializes untrusted data without sufficiently verifying that the resulting data will be valid.