Summary
A Remote Code Execution (RCE) vulnerability exists in Craft CMS 5.x and 4.x that bypasses the security fixes for GHSA-7jx7-3846-m7w7 and GHSA-255j-qw47-wjh5. This vulnerability can be exploited by any authenticated user with control panel access.
The existing patches add cleanseConfig() to assembleLayoutFromPost() and various FieldsController actions to strip Yii2 behavior/event injection keys (as and on prefixed keys). However, the fieldLayouts parameter in ElementIndexesController::actionFilterHud() is passed directly to FieldLayout::createFromConfig() without any sanitization, enabling the same behavior injection attack chain.
Impact
- Attack Type: Remote Code Execution (RCE)
- Authentication Required: Authenticated user with control panel access (
accessCp permission)
Vulnerability Details
Root Cause
In ElementIndexesController::actionFilterHud() (line 493-494), the fieldLayouts body parameter is passed to FieldLayout::createFromConfig() without cleanseConfig():
// ElementIndexesController.php:485-494
if ($conditionConfig) {
$conditionConfig = Component::cleanseConfig($conditionConfig); // conditionConfig IS cleansed
$condition = $conditionsService->createCondition($conditionConfig);
} else {
$condition = $this->elementType()::createCondition();
}
if (!empty($fieldLayouts)) {
// fieldLayouts is NOT cleansed!
$condition->setFieldLayouts(array_map(
fn(array $config) => FieldLayout::createFromConfig($config),
$fieldLayouts
));
}
Note the inconsistency: conditionConfig is sanitized with cleanseConfig(), but fieldLayouts is not.
Attack Chain
- Send a
fieldLayouts array containing config with "as <name>" prefixed keys
FieldLayout::createFromConfig($config) -> new self($config) -> Model::__construct($config)
App::configure($this, $config) processes each key
"as rce" key -> Component::__set("as rce", $value) -> Yii::createObject($value) -> instantiates AttributeTypecastBehavior and attaches it to the FieldLayout
"on *" key -> registers a wildcard event handler
parent::__construct() -> init() -> setTabs([]) -> getAvailableNativeFields() -> trigger(EVENT_DEFINE_NATIVE_FIELDS)
- The wildcard handler fires ->
AttributeTypecastBehavior::beforeSave() -> typecastAttributes()
$this->owner->typecastBeforeSave -> resolved via Component::__get() -> returns the command string from the behavior's own property
call_user_func([ConsoleProcessus::class, 'execute'], $command) -> shell_exec($command)
Prerequisites
- A user account with control panel access
References
Summary
A Remote Code Execution (RCE) vulnerability exists in Craft CMS 5.x and 4.x that bypasses the security fixes for GHSA-7jx7-3846-m7w7 and GHSA-255j-qw47-wjh5. This vulnerability can be exploited by any authenticated user with control panel access.
The existing patches add
cleanseConfig()toassembleLayoutFromPost()and variousFieldsControlleractions to strip Yii2 behavior/event injection keys (asandonprefixed keys). However, thefieldLayoutsparameter inElementIndexesController::actionFilterHud()is passed directly toFieldLayout::createFromConfig()without any sanitization, enabling the same behavior injection attack chain.Impact
accessCppermission)Vulnerability Details
Root Cause
In
ElementIndexesController::actionFilterHud()(line 493-494), thefieldLayoutsbody parameter is passed toFieldLayout::createFromConfig()withoutcleanseConfig():Note the inconsistency:
conditionConfigis sanitized withcleanseConfig(), butfieldLayoutsis not.Attack Chain
fieldLayoutsarray containing config with"as <name>"prefixed keysFieldLayout::createFromConfig($config)->new self($config)->Model::__construct($config)App::configure($this, $config)processes each key"as rce"key ->Component::__set("as rce", $value)->Yii::createObject($value)-> instantiatesAttributeTypecastBehaviorand attaches it to the FieldLayout"on *"key -> registers a wildcard event handlerparent::__construct()->init()->setTabs([])->getAvailableNativeFields()->trigger(EVENT_DEFINE_NATIVE_FIELDS)AttributeTypecastBehavior::beforeSave()->typecastAttributes()$this->owner->typecastBeforeSave-> resolved viaComponent::__get()-> returns the command string from the behavior's own propertycall_user_func([ConsoleProcessus::class, 'execute'], $command)->shell_exec($command)Prerequisites
References