74cm模板注入任意文件读取漏洞分析
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路径为:
但是骑士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
的方法,跟进该方法:
看到上面代码就明了了,直接先进行一个非空判断,不为空的情况下直接包含了该文件。