CVSS 10 满分漏洞:CVE-2025-32432 Craft CMS RCE 及未公开利用链深度解构

CVSS 10 满分漏洞:CVE-2025-32432 Craft CMS RCE 及未公开利用链深度解构

原创 KCyber 自在安全 2025-05-11 00:01

漏洞概述

近期 Craft CMS
 曝出了 CVSS 10
 分的高危漏洞 CVE-2025-32432
 ,该漏洞可以看成是 CVE-2023-41892
 的延续,两个漏洞都尝试利用 Yii
 框架 createObject
 函数触发可控类实例化来实现 RCE
 。
– • CVE-2023-41892
 漏洞通报: https://github.com/craftcms/cms/security/advisories/GHSA-4w8r-3xrw-v25g ,影响版本:>= 4.0.0-RC1, <= 4.4.14
 ;

  • • CVE-2025-32432
     漏洞通报: https://github.com/craftcms/cms/security/advisories/GHSA-f3gw-9ww9-jmc3 ,影响版本: >= 3.0.0-RC1, <= 3.9.14; >= 4.0.0-RC1, <= 4.14.14; >= 5.0.0-RC1, <= 5.6.16
     。

在理解漏洞触发原理的基础上,找到了一条新的未公开利用链,但是由于底层
 Yii
 框架的更新导致无法利用,这里一并分享给大家吧。## CVE-2023-41892

补丁对比

存在2
处修改。第一处位于ConditionsController
类,在beforeAction
函数中去掉了调用父类beforeAction
的代码:

第二处修改还是位于ConditionsController
,调用Component::cleanseConfig
对参数$config
进行过滤:

请求路由分析

ConditionsController
类继承了Controller
基类,首先尝试构造到达ConditionsController
的请求路由。Craft CMS
是基于Yii
框架进行的二次开发,在index.php
通过\yii\base\Application::run
进行初始化启动。一直往下走将在Application::_processActionRequest
函数中判断是否存在Action

跟进\craft\web\Request::getIsActionRequest
,内部调用checkIfActionRequest
来获取_isActionRequest
并返回:

在请求中添加action
参数,并使用/
对字符串进行分割:

进入\yii\base\Module::runAction
函数,内部将利用\yii\base\Module::createController
函数获取控制器对象 :

利用/
将$route
变量分割为$id
和$route
,然后通过createControllerByID
来提取控制器对象:

从createControllerByID
函数的实现来看,要求类名(a
变成了craft\controllers\AController
)必须位于命令空间craft\controllers
之下,并且继承\yii\base\Controller
类:

以AssetsController
为例重新构造请求进行调试分析,回到\yii\base\Module::runAction
函数:

进入\yii\base\Controller::createAction
,判断$id
生成的方法名是否存在:

更新请求action=assets/upload
,继续分析\yii\base\Controller::createAction
,内部将会调用父类的\yii\base\Controller::beforeAction
函数,至此完整路由的构造规则基本清晰。

ConditionsController 类实例化

ConditionsController
类满足上面分析的action
构造规则,并且该类重写了beforeAction
函数,可构造特定请求到达该函数:

尝试通过Json
解码提取config
参数并赋值给$baseConfig
,从$baseConfig
中提取键值key
等于name
的值并赋值给$config
,然后利用\craft\services\Conditions::createCondition
函数获取$this->_condition
变量,要求$config
对应的类必须继承于ConditionInterface
接口:

满足条件的子类共有 9
 个:

AddressCondition   \craft\elements\conditions\addressesAssetCondition   \craft\elements\conditions\assetsBaseCondition   \craft\base\conditionsCategoryCondition   \craft\elements\conditions\categoriesElementCondition   \craft\elements\conditionsElementConditionInterface   \craft\elements\conditionsEntryCondition   \craft\elements\conditions\entriesTagCondition   \craft\elements\conditions\tagsUserCondition   \craft\elements\conditions\users

随机选择一个构造新的请求:

进入\yii\BaseYii::configure
函数,内部遍历$baseConfig
对象并给ConditionInterface
接口对象(上面请求中为AddressCondition
)进行赋值:

AddressCondition
继承了\yii\base\Component
类,并且该类重写了__set
魔法函数,因此遍历过程中每进行一次不存在对象的赋值,都会触发__set
函数:

__set
函数中存在如下判断过程:当$name
中存在as 
字符串时,将会调用\yii\BaseYii::createObject
函数:

当存在class
或者__class
这样的key
时,将会调用\yii\di\Container::get
函数,而get
函数会通过build
来完成class
类的实例化:

class
 可控,只要找到合适的类就可能实现任意代码执行。

漏洞利用

Craft CMS
自带的FnStream
库,在PHP
反序列化gadget
构造中经常用到。其__destruct
函数中调用了call_user_func
,这里刚好可以用来触发RCE

CVE-2025-32432

补丁对比

在AssetsController.php
中,函数actionGenerateTransform
增加了对请求参数handle
的判断,将参数限定为字符串类型 :

触发点分析

与CVE-2023-41892
的触发点ConditionsController
类似,AssetsController
也继承于Controller
。actionGenerateTransform
函数分别提取assetId
和handle
请求参数,然后通过normalizeTransform
对handle
参数进行处理,注意到其中一个if
判断的分支:当handle
满足is_array
类型时,将实例化ImageTransform
对象,并且构造函数的输入参数为handle

ImageTransform
将调用父类Model
的__construct
函数,进而调用App::configure
,并且输入参数$config
来自于handle
可控,与CVE-2023-41892
一样,这里同样可以触发createObject
并完成类实例化:

新请求路由分析

参考 CVE-2023-41892
 ,尝试构造如下请求:

POST / HTTP/1.1Host: User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0Cookie: XDEBUG_SESSION=PHPSTORMConnection: closeContent-Type: application/x-www-form-urlencodedContent-Length: 31action=assets/generateTransform

在 createAction
 函数中判断 action
 参数的格式不符合正则规则,将 action
 修改为 assets/generate-transform
 即可:

if (preg_match('/^(?:[a-z0-9_]+-)*[a-z0-9_]+$/', $id)) {    $methodName = 'action' . str_replace(' ', '', ucwords(str_replace('-', ' ', $id))); // assets/generate-transform    if (method_exists($this, $methodName)) {        $method = new \ReflectionMethod($this, $methodName);        if ($method->isPublic() && $method->getName() === $methodName) {            return new InlineAction($id, $this, $methodName);        }    }}

此外,在基类 Controller
 的 beforeAction
 函数中还存在 CSRF
 检查,因此上述请求需携带 CSRF
 信息,修改格式如下:

POST / HTTP/1.1Host: User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0X-CSRF-Token: ***Cookie: CRAFT_CSRF_TOKEN=***;XDEBUG_SESSION=PHPSTORMConnection: closeContent-Type: application/x-www-form-urlencodedContent-Length: 32action=assets/generate-transform&assetId=1&handle=12345

成功进入actionGenerateTransform
函数,但是存在一个问题:修复补丁中限定handle
类型为string
,而通过上述请求方式传入的handle
正好也是string
,显然通过这种请求方式无法触发漏洞:

那还有没有其他的请求方式呢?抓取admin
登录页面,发现route
还可以通过GET
参数p
来获取,最终提取到UsersController
的actionLogin
进行处理:

经过分析发现,在_checkIfActionRequestInternal
函数开头还存在另一种提取route
的方式:

利用 _getQueryStringPath
 提取 GET
 参数 p
 的值并赋值给 _path
 ,然后通过 /
 进行分割并提取到对应的 Controller
 对象以及 action
 函数:

因此很容易构造参数 p
 来访问 actionGenerateTransform
 函数,此时 POST
 请求参数是 json
 格式,获取的 handle
 对象满足 is_array
 条件要求 :

成功实现类加载并触发 RCE
 :

漏洞复现

不成功的未公开利用链

只要找到新的参数可控的 createObject
 触发点,那么就可以绕过补丁实现 RCE
 。这里给出一条未公开的利用链:

\craft\controllers\FieldsController::actionApplyLayoutTabSettings    │      └─→ \craft\models\FieldLayoutTab::__construct//getRequiredBodyParam('config');        │          └─→ \craft\base\Model::__construct            │              └─→ \craft\helpers\App::configure                │                  └─→ \yii\base\Component::__set                    │                      └─→ \yii\BaseYii::createObject//__class                         │                           └─→ \yii\di\Container::get                             │                               └─→ \yii\di\Container::build                                 │                                   └─→ \ReflectionClass::newInstanceArgs // class

需要管理员认证并且开启 allowAdminChanges
 配置才能访问,该利用链在老版本中验证成功,但是在最新版本中却返回异常:

在 Craft CMS
 代码中没有找到更新,最终发现底层 Yii
 框架在 v2.0.52
 版本中更新了 Component.__set
 函数,里面新增了 __class
 参数判断,限定了 createObject
 中可实例化类的类型:

由于传播、利用此文档提供的信息而造成任何直接或间接的后果及损害,均由使用本人负责,公众号及文章作者不为此承担任何责任。