前言
最近刚刚简单学习了thinkphp,所以就看了看thinkphp爆出的代码执行漏洞。第一次看这种代码,所以是根据大佬们所讲的,加大佬们的payload,加手工调试,总算是找到了一些流程
thinkphp漏洞 一
漏洞版本
thinkphp5.1.* 版本
触发条件
thinkphp5 设置 error_reporting(0)
环境
thinkphp5.1.31+php7.2.13+apache
漏洞分析
漏洞文件:\thinkphp\library\think\Request.php
这个文件是Request类,处理对网站发起的请求
漏洞代码(其中有我加的调试代码)
public function method($origin = false)
{
print_r("start---method ");
if ($origin) {
// 获取原始请求类型
return $this->server('REQUEST_METHOD') ?: 'GET';
} elseif (!$this->method) {
if (isset($_POST[$this->config['var_method']])) {
$this->method = strtoupper($_POST[$this->config['var_method']]);
$method = strtolower($this->method);
$this->{
$method} = $_POST;
print_r('start------覆盖 ');
} elseif ($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')) {
$this->method = strtoupper($this->server('HTTP_X_HTTP_METHOD_OVERRIDE'));
} else {
$this->method = $this->server('REQUEST_METHOD') ?: 'GET';
echo "server2";
}
}
return $this->method;
}
这里存在一个变量覆盖漏洞,$this->config[‘var_method’]这是一个常量,这个常量定义在\think\config\app.php中,也就是$this->config[‘var_method’]=_method,我们以POST传入的_method 的值会被先转化为大些,然后转化为小写,赋值给$method,最后会执行$this->{$method} = $_POST,既然_method的值是post传入的,那就是可控的,也就是说$this->{$method} = P O S T 中 _POST中 POST中method是可控的,也就是说$method 可以是request类中任何变量,最后会导致request类中任何变量可以被覆盖。
举个列子
<?php
class fugai
{
public $filter=123;
public $method=456;
public function __construct()
{
$this->method = strtoupper($_POST['_method']);
$method = strtolower($this->method);
$this->{
$method}= $_POST;
}
public function print_fugai()
{
echo '$this->filter=';
print_r($this->filter);
echo '$this->method=';
print_r($this->method);
}
}
$a =new fugai();
$a->print_fugai();
?>
最后$this->filter的值被覆盖为图上的值,这样就能覆盖类中任意变量
接下来是利用函数,其实通过代码审计工具也能找到,函数中的call_user_func是可以造成代码执行的函数,其中$filter可以是php自带函数(可以是危险函数,但不包括eval),$value是传入函数的值。
private function filterValue(&$value, $key, $filters)
{
$default = array_pop($filters);
foreach ($filters as $filter) {
if (is_callable($filter)) {
// 调用函数或者方法过滤
$value = call_user_func($filter, $value);
} elseif (is_scalar($value)) {
if (false !== strpos($filter, '/')) {
// 正则过滤
if (!preg_match($filter, $value)) {
// 匹配不成功返回默认值
$value = $default;
break;
}
} elseif (!empty($filter)) {
// filter函数不存在时, 则使用filter_var进行过滤
// filter为非整形值时, 调用filter_id取得过滤id
$value = filter_var($value, is_int($filter) ? $filter : filter_id($filter));
if (false === $value) {
$value = $default;
break;
}
}
}
}
return $value;
}
调试查看哪里调用了函数filterValue,找到input函数和cookie ,函数cookie函数是获取cookie参数,input函数是获取变量。进行调试(print就行)发现是Input函数对filterValue进行了调用,跟进Input函数
调试并找到调用filterValue的函数array_walk_recursive。
public function input($data = [], $name = '', $default = null, $filter = '')
{
print_r('start input');
if (false === $name) {
// 获取原始数据
return $data;
}
$name = (string) $name;
if ('' != $name) {
// 解析name
if (strpos($name, '/')) {
list($name, $type) = explode('/', $name);
}
$data = $this->getData($data, $name);
if (is_null($data)) {
return $default;
}
if (is_object($data)) {
return $data;
}
}
// 解析过滤器
echo '$this->filter';
$filter = $this->getFilter($filter, $default);
//print_r($this->filter);
//print_r($filter);
if (is_array($data)) {
array_walk_recursive($data, [$this, 'filterValue'], $filter);
if (version_compare(PHP_VERSION, '7.1.0', '<')) {
// 恢复PHP版本低于 7.1 时 array_walk_recursive 中消耗的内部指针
$this->arrayReset($data);
}
} else {
$this->filterValue($data, $name