• PHP反序列化漏洞


    一、序列化,反序列化 

    • 序列化:将php对象压缩并按照一定格式转换成字符串过程
    • 反序列化:从字符串转换回php对象的过程
    • 目的:为了方便php对象的传输和存储

    seriallize()       传入参数为php对象,序列化成字符串

    unserialize()    传入字符串,反序列化成一个对象

    下图,上面是php对象,下面是 序列化结果 

     强调:序列化的过程,只序列化属性,不序列化函数

    二、反序列化攻击

    利用unserilize()接受的参数是用户可控,攻击者输入精心构造的字符串,再转换成对象的过程中实现攻击

    只序列化属性,不序列化方法,所有对象的属性是唯一的攻击入口

    魔术方法(以__开头的函数)

    1.__construct():
    构造函数,当一个对象被实例化时,就会被调用

    1. class A
    2. {
    3. function __construct()
    4. {
    5. echo "this is a construct function":
    6. }
    7. }
    8. $a = new A();
    9. ?>

     当$a = new A() 这条语句被执行的时候,__construct()方法就会被调用

    2.__destruct():

    析构函数,当代码执行结束,对象所占用的空间被回收的时候,回自动调用析构函数

    1. class A
    2. {
    3. function __construct()
    4. {
    5. echo "this is a construct function";
    6. }
    7. function __destruct()
    8. {
    9. echo "this is a destruct function";
    10. }
    11. }
    12. $a = new A();
    13. ?>

    这个方法,不管是啥情况,只要代码执行,肯定有结束的时候,就一定会调用析构函数 

    3.__sleep():

    这个函数的根本目的是返回一个需要被序列化的’属性‘数组
    在对象进行序列化的过程,__sleep()函数将被调用

    1. class A
    2. {
    3. private $test;
    4. public $test2;
    5. public function __construct($test)
    6. {
    7. $this->test = $test;
    8. }
    9. public function __sleep()
    10. {
    11. echo "this is a sleep function";
    12. return array('test'); //这里必须返回一个数值,里面的参数表示返回的属性名称
    13. }
    14. }
    15. $a = new A("Aurora");
    16. echo serialize($a);
    17. ?>

    在代码中有serialize()方法的出现,__sleep()这个魔术方法一定会被调用

    4.__wakeup():
    wakeup()与sleep()正好相对应

    是在反序列化的过程会被调用

    当unserialize()函数出现,__wakeup()这个魔术方法一定会被调用

    注意:反序列化看似是构造一个对象,但并没有调用到constant方法,而是调用__wakeup()方法

    5.__toString()方法

    当出现,把一个对象当作字符串来使用,就会调用该方法

    1. class A
    2. {
    3. private $test;'
    4. public function __construct($test)
    5. {
    6. $this->test = $test;
    7. }
    8. function __toString()
    9. {
    10. $str = "this is a toString function";
    11. return $str;
    12. }
    13. }
    14. $a = new A("Aurora");
    15. echo $a; //这里将$a做为字符串来输出
    16. ?>

    6.__invoke():

    当把一个对象当作函数来调用的时候,就会自动调用invoke()方法

    1. class A
    2. {
    3. private $test;
    4. public function __construct($test)
    5. {
    6. $this->test = $test;
    7. }
    8. function __invoke()
    9. {
    10. echo = "this is a invoke function";
    11. }
    12. }
    13. $a = new A("Aurora");
    14. $a(); //$a是一个对象,但却用$a()调用方法的方法使用它
    15. ?>

    上面$a()就是将对象作为函数来调用的例子

    7.__call():

    调用对象中不存在的方法,就会调用call函数

    1. class A
    2. {
    3. private $test;
    4. public function __construct($test)
    5. {
    6. $this->test = $test;
    7. }
    8. function __call($funName,$arguments) //参数funName指的是函数名,arguments指的是参数
    9. {
    10. echo "你所调用的方法:“.$funName."(参数:"; //输出调用不存在的方法名
    11. print_r($arguments);
    12. echo ")不存在!
      \n"; //结束换行
    13. }
    14. $a = new A("Aurora");
    15. $a->test('no','this','function'); //可以看到A类中并没有test()方法
    16. ?>

    三、案例

    1.

    首先要找到攻击的入口,对于上面的php代码,攻击的入口是unserialize($_GET['test']);

    输入的参数的一串被序列字符串,里面创建一个Aurora对象的test变量是一个Evil对象,Evil对象中的test2属性是我们想要执行的系统命令

    生成payload

    1. class Aurora{
    2. private $test;
    3. function __construct()
    4. {
    5. $this->test = new Evil();
    6. }
    7. }
    8. class Evil{
    9. var $test2 = "ls";
    10. }
    11. $Aurora = new Aurora();
    12. $data = serialize($Aurora);
    13. echo($data);
    14. ?>

     使用在线php工具进行运行就能得到payload