Craft CMS 前台RCE(CVE-2023-41892)漏洞分析
Craft CMS 前台RCE(CVE-2023-41892)漏洞分析
项目介绍
Craft CMS(Content Management System)是一个强大、灵活且易用的内容管理系统,旨在为开发人员、设计师和内容创作者提供出色的使用体验和定制能力。Craft CMS 项目(以下统一简称项目)在 GitHub 拥有 3.1k 的 star。项目地址为:https://github.com/craftcms/cms
ZoomEye语法:app:”Craft CMS”
漏洞介绍
漏洞描述
Craft CMS前台存在一个可以创建任意对象的接口,未授权的攻击者可以通过该接口创建可执行恶意代码的FnStream对象,从而在服务器上执行任意代码。
Payload
1 | action=conditions/render&test=craft\elements\conditions\tags\TagCondition&config={"name":"test","as sdyz":{"class":"\\GuzzleHttp\\Psr7\\FnStream","__construct()": [{"close":null}],"_fn_close":"phpinfo"}} |
影响版本
>= 4.0.0-RC1, <= 4.4.14
安全版本
Craft CMS 4.4.15
漏洞复现
漏洞分析
以下分析基于 4.4.14 版本进行。
对比 4.4.14 与 4.4.15 版本可发现,项目增加了一道过滤程序 Component::cleanseConfig,清洗前端传来的数据。
而这个过滤程序主要工作就是移除前端传来的数据中以 on 和 as 开头的键值对。
再看回src/contrillers/ConditionsController/berforeAction()。该动作用于访问conditions/render动作前进行前期检查。这里调用 Craft::configure 实例化了一个类,同时利用前端传来的数据对该实例进行了一些设置。类名与参数皆是由前端传入参数控制的。
Craft::configure 是Craft的一个静态方法,继承关系为Craft -> Yii -> BaseYii。主要功能是设置对象的属性,传入一个对象和一个待设置属性的字典,返回设置好的对象。
众所周知,PHP在为一个对象设置不存在的属性时,会调用类中的__set()方法来处理。结合Payload来看,我们在上面实例化的类为craft\elements\conditions\tags\TagCondition。
这个类继承自ElementCondition,完整的继承关系为ElementCondition -> BaseCondition -> Component -> Model -> \yii\base\Model -> \yii\base\Component -> \yii\base\BaseObject。追溯倒\yii\base\BaseObject,我们可以看到被实现的__set()方法。
yii框架会将设置on 开头元素的行为视为注册事件处理程序,设置as 开头元素的行为视为要附加行为。可以注意到这里调用了Yii::createObject来创建一个对象。追踪Yii::createObject方法,可以发现其最后调用了call_user_func来创建对象。调用链为Yii::createObject() -> Container::get() -> call_user_func()
综上我们可以总结该漏洞的成因为:为继承自\yii\base\Model的对象设置元素时,没有过滤特殊元素(as ***、on ***)而导致的任意对象创建。
修复建议
- 厂商已修复该漏洞,请及时升级至 4.4.15 及以上版本。
一些杂谈
\yii\base\Model的这个特性直到当前 2.0.49 仍未被更改。后续在审计 yii 框架的项目时可以重点关注继承自\yii\base\Model的类,检查是否存在同款漏洞。该漏洞的利用需要调用继承自
\yii\base\Model的类即可,不一定非要craft\elements\conditions\tags\TagCondition类。整个项目存在多个类,如果需要编写拦截/检测规则,需要考虑更多的情况。Payload中创建的\GuzzleHttp\Psr7\FnStream对象有什么特殊之处呢?这玩意的析构函数就是执行
_fn_close()函数。













