thinphp审计-反序列化5.1.x

之前打比赛的时候碰到cms题目大多是去google现成的exp,现在正好花出时间来自己审一边(由于**法律原因,后文中的EXP就不公开了),新年快乐,武汉加油

环境

php7.2 + apache + thinkphp5.1.37

过程

(1)先在全局搜索__destruct 进行查看,这里可以使用的析构方法在Windows.php,可以利用Windows类

追溯这里的removeFiles方法

1
2
3
4
5
6
7
8
9
private function removeFiles()
{
foreach ($this->files as $filename) {
if (file_exists($filename)) {
@unlink($filename);
}
}
$this->files = [];
}

这里存在file_exists方法,要利用的话,就需要借此调用__toString类来执行,于是就全局搜索__toString方法,

这里我们可以利用的__toString方法在Conversion.php

我们要找的是一个这种类型的样子的方法$a->function($b)并且我们可以控制$a$b

我们可以在toArray方法中找到$relation->visible($name)

先看$relation$name是否可控,追溯这个变量可以发现,$name变量由$this->append获取

再看$relation变量由getAttr方法中获取

饿

可以看到我们可以通过控制$this->data来控制$relation变量

要注意的是Conversion.php是一个trait类,所以我们需要找到复用了它的类,Model类

pop链到这里有两个方向选择,看一看有没有其他类的visiable方法可以被利用,然而并没有,那就要走另一个方向了,找__call方法,全局搜索function __call,我们可以利用的是Request类,来看一下__call()方法

我们可以利用这里call_user_func_array方法进行命令执行,先不管我们能否控制$hook[$method]$args会被array_unshift插入一个类对象,这样的话方法无论如何都会执行失败,所以这里不能直接利用,我们需要找一个不受$args变量影响的方法,在Request类中有一个input的方法是一个很好的构链方法,调用了以后直接相当于call_user_func($filter,$data),但我们不能直接调用,因为$arg的第一个变量会导致input方法执行失败,我们要去找一个调用了input方法的方法。

在Request中,param方法调用了input方法,再去查找调用了param方法的方法,这里可用的是isAjax方法

这里的$config['var_ajax']控制param$name参数是isAjax方法中的param方法的$name方法,也就控制了$this->param由此就控制了input方法的$data参数,然后$filter变量是可控的。自此pop链构造完成。

执行成功,下面是整条pop链

image-20200214230229536

写在后面

至于为什么input是一个很好的Gadget

先来看一下input函数

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
public function input($data = [], $name = '', $default = null, $filter = '')
{
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;
}
}

// 解析过滤器
$filter = $this->getFilter($filter, $default);

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, $filter);
}

if (isset($type) && $data !== $default) {
// 强制类型转换
$this->typeCast($data, $type);
}

return $data;
}

input中可能引起RCE的就是array_walk_recursive函数,也就是执行了filterValue($value,$key,$filter)我们在追溯一下filterValue函数

image-20200214230530493

这里调用了call_user_func函数

所以input($data = [], $name = '', $default = null, $filter = '')就相当于call_user_func($filter,$data)