image.png

简介

  • 序列化就是将数据转换成一种可逆的数据结构,逆向的过程就叫做反序列化
  • 比如:快递一张桌子,一般都会把它拆掉成板子,再装到箱子里面,就可以快递寄出去了,这个过程就类似我们的序列化的过程(把数据转化为可以存储或者传输的形式)。当收到货后,就需要把这些板子组装成桌子的样子,这个过程就像反序列的过程(转化成当初的数据对象)。

    PHP将数据序列化和反序列化会用到两个函数

    1. serialize 将对象格式化成有序的字符串
    2. unserialize 将字符串还原成原来的对象
  • 序列化的目的是方便数据的传输和存储,在PHP中,序列化和反序列化一般用做缓存,比如session缓存,cookie等。

常见的序列化格式

  • 二进制格式
  • 字节数组
  • json字符串
  • xml字符串

PHP序列化

序列化函数serialize()

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class Ctf{
public $flag='flag{****}';
public $name='cxk';
public $age='10';
}
$ctfer=new Ctf(); //实例化一个对象
$ctfer->flag='flag{adedyui}';
$ctfer->name='Sch0lar';
$ctfer->age='18'
echo serialize($ctfer);
?>
  • //输出结果:
  • O:3:"Ctf":3{s:4:"flag";s:13:"flag{abedyui}";s:4:"name";s:7:"Sch0lar";s:3:"age";s:2:"18";}

    O代表对象,因为我们序列化的是一个对象;序列化数组的话则用A来表示
    3代表类的名字长三个字符
    Ctf 是类名
    3代表这个类里有三个属性(三个变量)
    s代表字符串
    4代表属性名的长度
    flag是属性名
    s:13:”flag{adedyui}” 字符串,属性长度,属性值

  • serialize() 函数会检查类中是否存在一个魔术方法 __sleep()
  • 如果存在,__sleep()方法会先被调用,然后才执行序列化操作。
  • 可以在__sleep()方法里决定哪些属性可以被序列化。如果没有__sleep()方法则默认序列化所有属性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <?php
    class Ctf{
    public $flag='flag{****}';
    public $name='cxk';
    public $age='10';
    public function __sleep(){
    return array('flag','age');
    }
    }
    $ctfer=new Ctf();
    $ctfer->flag='flag{abedyui}';
    $ctfer->name='Sch0lar';
    $ctfer->age='18'
    echo serialize($ctfer);
    ?>
  • // 输出结果:
  • O:3:"Ctf":2:{s:4:"flag";s:13:"flag{abedyui}";s:3:"age";s:2:"18";}
  • 这里__sleep()方法决定了哪些属性被序列化

访问控制修饰符

  • 根据访问控制修饰符的不同序列化后的属性长度属性值会有所不同

    public(公有)
    protected(受保护) // %00*%00属性名
    private(私有的) // %00类名%00属性名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <?php
    class Ctf{
    public $name='Sch0lar';
    protected $age='19';
    private $flag='get flag';
    }
    $ctfer=new Ctf(); //实例化一个对象
    echo serialize($ctfer);
    ?>
  • //输出结果
  • O:3:"Ctf":3:{s:4:"name";s:7:"Sch0lar";s:6:"*age";s:2:"19";s:9:"Ctfflag";s:8:"get flag";}
  • age的长度为6,前面有两个%00空白符,Ctfflag同理

PHP反序列化

反序列化函数unserialize()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class Ctf{
public $flag='flag{****}';
public $name='cxk';
public $age='10';
}
$ctfer=new Ctf(); //实例化一个对象
$ctfer->flag='flag{adedyui}';
$ctfer->name='Sch0lar';
$ctfer->age='18'
$str=serialize($ctfer);
echo '<pre>';
var_dump(unserialize($str))
?>
  • unserialize()会检查类中是否存在一个__wakeup魔术方法
  • 如果存在则会先调用__wakeup()方法,再进行序列化
  • 可以在__wakeup()方法中对属性进行初始化、赋值或者改变。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <?php
    class Ctf{
    public $flag='flag{****}';
    public $name='cxk';
    public $age='10';
    public function __wakeup(){
    $this->flag='no flag'; //在反序列化时,flag属性将被改变为“no flag”
    }
    }
    $ctfer=new Ctf(); //实例化一个对象
    $ctfer->flag='flag{adedyui}';
    $ctfer->name='Sch0lar';
    $ctfer->age='18'

    $str=serialize($ctfer);
    echo '<pre>';
    var_dump(unserialize($str));
    ?>
  • 反序列化之前重新给flag属性赋值

反序列化POP链

  • unserialize()反序列化函数用于将单一的已序列化的变量转换回 PHP 的值。
  • 当反序列化参数可控时,可能会产生PHP反序列化漏洞
  • 在反序列化中,我们所能控制的数据就是对象中的各个属性值,所以在PHP的反序列化中有一种漏洞利用方法叫做 “面向属性编程POP”,面向对象编程从一定程度上来说,就是完成类与类之间的调用。POP链起于一些小的“组件”,这些小“组件”可以调用其他的“组件”
  • 在PHP中,“组件”就是那些魔术方法(如:wakeup()或destruct)
  • 面向属性编程(Property-Oriented Programing)常用于上层语言构造特定调用链的方法,与二进制利用中的面向返回编程(Return-Oriented Programing)的原理相似,都是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链。在控制代码或者程序的执行流程后就能够使用这一组调用链做一些工作了。

PHP反序列化漏洞

原理

  • 未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导致代码执行,SQL注入,目录遍历等不可控后果。
  • 在反序列化的过程中自动触发了某些魔术方法。

触发条件

  • unserialize函数的参数、变量可控,php文件中存在可利用的类,类中有魔术方法

魔术方法

  • __construct() 当对象创建(new)时会自动调用。但在 unserialize() 时是不会自动调用的。
  • __destruct() 当一个对象销毁(反序列化)时被调用
  • __toString()当一个对象被当作一个字符串使用时被调用
  • __sleep()在对象在被序列化之前立即运行
  • __wakeup()将在序列化之后立即被调用
  • 而在反序列化时,如果反序列化对象中存在魔法函数,使用unserialize()函数同时也会触发。这样,一旦我们能够控制unserialize()入口,那么就可能引发对象注入漏洞。

绕过魔法方法的反序列化漏洞

  • 漏洞CVE-2016-7124

__wakeup()

  • 将在序列化之后立即被调用
  • 当序列化字符串表示对象属性个数的数字值大于真实类中属性的个数时就会跳过__wakeup的执行

__destruct()

1
2
3
4
5
6
7
8
9
<?php
class Example {
var $var = '';
function __destruct() {
eval($this->var);
}
}
unserialize($_GET['a']);
?> 
  • 要让eval($this->var);执行我们的恶意代码,我们就需要修改属性$var的值。接下来构造序列化数据:
    1
    2
    3
    $obj = new Example();
    $obj->var='phpinfo()';
    var_dump(serialize($obj));
  • 输出O:7:"Example":1:{s:3:"var";s:9:"phpinfo()";}
  • 输出执行eval(phpinfo())

__toString()

  • __toString当对象被当作一个字符串使用时候调用(不仅仅是echo的时候,比如file_exists()判断也会触发)