74cm模板注入任意文件读取漏洞分析

english-cocker-spaniel-5937757_1920

74cmsssti 文件包含漏洞的源码审计分析流程

74cmsssti+文件包含漏洞

环境复现

直接官网下载对应版本的源码包,使用docker部署即可。

docker run –name 74cms -itd -P 74cmsssti_readfile:latest

版本影响:74cms < 6.0.48

漏洞利用

启动docker,进行站点安装,数据库:ctf ctf ; 后台admin admin;

安装完成后请求,发送如下请求:

http://x.x.x.x/index.php?m=home&a=assign_resume_tpl

POST:variable=1&tpl=/r/n<qscms/company_show 列表名=”info” 企业id=”$_GET[;id;]”/>

触发报错,进入容器发现日志记录功能已经记录报错日志,位置在:/data/Runtime/Logs/Home

我们看到构造的payload内容也被成功写入进日志,并且日志的命令格式为:年_月_日.log

尝试包含日志,成功利用

http://x.x.x.x/index.php?m=home&a=assign_resume_tpl

POST:variable=1&tpl=data/Runtime/Logs/Home/21_01_06.log

漏洞分析

根据官方公告说明在/Application/Common/Controller/BaseController.class.php文件的assign_resume_tpl函数因为过滤不严格,导致了模板注入,可以进行远程命令执行。

骑士cms采用的是thinkphp3.2.3版本的框架,该框架标准的url路径为:

http://x.x.x.x/index.php/模块/控制器/操作

但是骑士cms采用的是普通模式,即使用传统的GET传参方式来指定当前的访问的模块和操作,可以通过/thinkphp/Conf/convention.php查看设置:

可以发现,使用m获取模块,c获取控制器,a获取操作:?m=&c=&a=&var1=&var2=

清楚最基本的传参格式后,我们来看整个漏洞的利用过程。

跟进/Application/Common/Controller/BaseController.class.php中的assign_resume_tpl方法:

此处传入两个变量,其中变量$tpl传入到fetch方法,跟进该方法,进入/ThinkPHP/Library/Think/View.class.php:

首先判断模板文件是否为空,如果不为空,继续判断是否使用了原生模板,查看配置文件/ThinkPHP/Conf/convention.php

通过配置文件发现骑士cms启用的是Think模板引擎,因此判断进入else:

这里将值带入数组,并传入Hook::listen 并解析view_parse标签,跟进/ThinkPHP/Library/Think/Hook.class.php方法:

通过代码我们可以发现,Hook::listen()方法,该方法会查找$tags是否绑定view_parse方法,然后foreach遍历$tags属性,并执行Hook::exec方法,跟进该方法:

通过代码我们发现exec方法会检查行为名称,如果含有Behavior关键字,入口方法必须是run方法;这里run方法用的所有参数是在Hook::listen时传递的。

此时我们去寻找Hook的配置文件,在/ThinkPHP/Mode/common.php

从配置文件可以看到view_parase标签执行了ParseTemplateBehavior这个类,因为这边已经定义所有的行为执行入口均为run方法,所以这边直接分析run方法即可;跟进这个类/ThinkPHP/Library/Behavior/ParseTemplateBehavior.class.php

阅读以上代码发现第一次解析时,是没有缓存文件的,所以会调用template执行fetch方法:

跟进文件/ThinkPHP/Library/Think/Template.class.php

这里我们看到fetch方法调用了loadTemplate方法,这里跟进该方法:

我们发现变量$templateFile被赋值给了$tmplContent,然后该变量在编译模板内容时赋值了方法compiler()方法,跟进该方法:

分析该段代码我们发现,这里传入的模板内容未经过任何过滤就拼接到了$tmplContent变量中

然后回归到loadTemplate方法,跟踪到下面,看到有编译模板内容的逻辑代码如下:

这里我们看到的是对模板进行缓存处理,然后返回模板的缓存文件名

继续回归到此前fetch方法:

我们发现返回的缓存文件名进入到Storage::load的方法,跟进该方法:

看到上面代码就明了了,直接先进行一个非空判断,不为空的情况下直接包含了该文件。

整体利用链

作者

丨greetdawn丨

发布于

2021-01-05

更新于

2022-04-01

许可协议

评论