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
中可实例化类的类型:
由于传播、利用此文档提供的信息而造成任何直接或间接的后果及损害,均由使用本人负责,公众号及文章作者不为此承担任何责任。