资讯专栏INFORMATION COLUMN

PHP反序列化漏洞系列之–PHP序列化和反序列化原理

caozhijian / 1522人阅读

0.前言

本文为笃行日常学习记录,web安全php漏洞系列。

对象的序列化和反序列化作用就不再赘述,php中序列化的结果是一个php自定义的字符串格式,有点类似json.

我们在任何语言中设计对象的序列化和反序列化都需要解决几个问题

把某个对象序列化之后,序列化的结果有自描述的功能(从序列化的结果中知道这个对象的具体类型,

知道类型还不够,当然还需要知道这个类型所对应具体的值).

序列化时的权限控制,可以自定义序列化字段等,例如golang中的做的就非常方便.

时间性能问题:在某些性能敏感的场景下,对象序列化就不能拖后腿,例如:高性能服务(我经常使用protobuf来序列化).

空间性能问题:序列化之后的结果不能太长,比如内存中一个int对象,序列化之后数据长度变成了10倍int的长度,那这个序列化算法是有问题的.

本文仅仅从php代码角度来解释php中序列化和反序列化的过程.,记住一点序列化和反序列化操作的仅仅是对象的数据,这一点有面向对象开发经验的都应该容易理解.

1.序列化serialize和反序列化方法unserialize

php原生提供了对象序列化功能,不像c++ ……^_^. 用起来也非常简单,就两个接口.

class fobnn
{
    public  $hack_id;
    private $hack_name;
    public function __construct($name,$id)
    {
        $this->hack_name = $name;
        $this->hack_id = $id;
    }
    public function print()
    {
        echo $this->hack_name.PHP_EOL;
    }
}

$obj = new fobnn("fobnn",1);
$obj->print();
$serializedstr = serialize($obj); //通过serialize接口序列化
echo $serializedstr.PHP_EOL;;
$toobj = unserialize($serializedstr);//通过unserialize反序列化
$toobj->print();
fobnn
O:5:"fobnn":2:{s:7:"hack_id";i:1;s:16:"fobnnhack_name";s:5:"fobnn";}
fobnn

看到第二行的输出,这个字符串就是序列化的结果,这个结构其实很容读懂,可以发现是通过对象名称/成员名称来映射的,当然不同访问权限的成员序列化之后的标签名称略有不同.

根据我上面讲到的3个问题,那么我们可以来看看

1.自描述功能

O:5:"fobnn":2 其中o就表示了object类型,且类型名称为fobnn, 采用这种格式,后面的2表示了有2个成员对象.

关于成员对象,其实也是同一套子描述,这是一个递归的定义.

自描述的功能主要是通过字符串记录对象和成员的名称来实现.

2.性能问题

php序列化的时间性能本文就不分析了,详见后面,但序列化结果其实类似json/bson定义的协议,有协议头,协议头说明了类型,协议体则说明了类型所对应的值,并不会对序列化结果进行压缩.

2.反序列化中的魔术方法

对应上述说的第二个问题,其实php中也有解决方法,一种是通过魔术方法,第二种则是自定义序列化函数.先来介绍下魔术方法 __sleep__wakeup

http://php.net/manual/en/lang...

http://php.net/manual/en/lang...

class fobnn
{
    public  $hack_id;
    private $hack_name;
    public function __construct($name,$id)
    {
        $this->hack_name = $name;
        $this->hack_id = $id;
    }
    public function print()
    {
        echo $this->hack_name.PHP_EOL;
    }

    public  function __sleep()
    {
        return array("hack_name");
    }

    public  function __wakeup()
    {
        $this->hack_name = "haha";
    }
}

$obj = new fobnn("fobnn",1);
$obj->print();
$serializedstr = serialize($obj);
echo $serializedstr.PHP_EOL;;
$toobj = unserialize($serializedstr);
$toobj->print();
fobnn
O:5:"fobnn":1:{s:16:"fobnnhack_name";s:5:"fobnn";}
haha

在序列化之前会先调用__sleep返回的是一个需要序列化的成员名称数组,通过这样我们就可以控制需要序列化的数据,案例中我只返回了hack_name,可以看到结果中只序列化了hack_name成员.

在序列化完成之后,会跳用__wakeup 在这里我们可以做一些后续工作,例如重连数据库之类的.

3.自定义Serializable接口

自定义序列化接口 http://php.net/manual/en/clas...

interface Serializable {
abstract public string serialize ( void )
abstract public void unserialize ( string $serialized )
}

通过这个接口我们可以自定义序列化和反序列化的行为,这个功能主要可以用来自定义我们的序列化格式.

class fobnn implements Serializable
{
    public  $hack_id;
    private $hack_name;
    public function __construct($name,$id)
    {
        $this->hack_name = $name;
        $this->hack_id = $id;
    }
    public function print()
    {
        echo $this->hack_name.PHP_EOL;
    }

    public  function __sleep()
    {
        return array("hack_name");
    }

    public  function __wakeup()
    {
        $this->hack_name = "haha";
    }

    public function serialize()
    {
        return json_encode(array("id" => $this->hack_id ,"name"=>$this->hack_name ));
    }

    public function unserialize($var)
    {
        $array = json_decode($var,true);
        $this->hack_name = $array["name"];
        $this->hack_id = $array["id"];
    }
}

$obj = new fobnn("fobnn",1);
$obj->print();
$serializedstr = serialize($obj);
echo $serializedstr.PHP_EOL;;
$toobj = unserialize($serializedstr);
$toobj->print();
fobnn
C:5:"fobnn":23:{{"id":1,"name":"fobnn"}}
fobnn

当使用了自定义序列化接口之后,我们的魔术方法就没用了.

4.PHP动态类型和PHP反序列化

既然上文中提到的自描述功能,那么序列化结果中保存了对象的类型,且php是动态类型语言,那么我们就可以来做个简单的实验.

class fobnn
{
    public  $hack_id;
    public $hack_name;
    public function __construct($name,$id)
    {
        $this->hack_name = $name;
        $this->hack_id = $id;
    }
    public function print()
    {
        var_dump($this->hack_name);
    }
}

$obj = new fobnn("fobnn",1);
$obj->print();
$serializedstr = serialize($obj);
echo $serializedstr.PHP_EOL;;
$toobj = unserialize($serializedstr);
$toobj->print();

$toobj2 = unserialize("O:5:"fobnn":2:{s:7:"hack_id";i:1;s:9:"hack_name";i:12345;}");
$toobj2->print();

我们修改hack_name反序列化的结果为int类型, i:12345

string(5) "fobnn"
O:5:"fobnn":2:{s:7:"hack_id";i:1;s:9:"hack_name";s:5:"fobnn";}
string(5) "fobnn"
int(12345)

可以发现,对象成功序列化回来了!并且可以正常工作!. 当然php的这种机制提供了灵活多变的语法,但也引入了安全风险. 后续继续分析php序列化和反序列化特性带来的安全问题.

最后 ending…如有不足请指点,亦可留言或联系 fobcrackgp@163.com.
本文为笃行原创文章首发于大题小作,永久链接:PHP反序列化漏洞系列之--PHP序列化和反序列化原理

https://www.ifobnn.com/phpserialize.html

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/26328.html

相关文章

  • PHP列化列化注意什么

    摘要:和函数这两个是序列化和反序列化中数据的常用函数。序列化数组输出结果反序列化输出结果当数组值包含如双引号单引号或冒号等字符时,它们被反序列化后,可能会出现问题。序列化反序列化但是编码将增加字符串的长度。序列化数组输出结果反序列化 序列化是将变量转换为可保存或传输的字符串的过程;反序列化就是在适当的时候把这个字符串再转化成原来的变量使用。这两个过程结合起来,可以轻松地存储和传输数据,使程序...

    Tangpj 评论0 收藏0
  • Laravel 5.5 升级到 5.5.42 后遇到的 Cookie 列化问题

    摘要:查阅官方文档后得知,新版为了防止对象的序列化反序列化漏洞被利用,不再对值进行自动的序列化和反序列化处理。举个栗子更新到后,因为不再自动对值进行序列化处理,而只能加密字符串数据,这个时候程序就会抛出错误。 最近手残升级了项目里 Laravel 的小版本号(v5.5.39 => v5.5.45),这不升级则已,一升级就出了问题! Sentry 平台上提示错误:openssl_encrypt...

    jollywing 评论0 收藏0
  • golang实现php里的serialize()和unserialize()序列序列方法

    摘要:实现里的安装用法世界上最好的语言世界上最好的语言世界上最好的语言地址 Golang 实现 PHP里的 serialize() 、 unserialize() 安装 go get -u github.com/techoner/gophp 用法 package main import ( fmt github.com/techoner/gophp/serialize )...

    TwIStOy 评论0 收藏0
  • PHP 批斗大会缺失的异常

    摘要:背后性能影响还是挺大的。缺失的异常刚开始写代码的时候一直不明白为什么要用异常,感觉就能搞定了,为什么还要多此一举,现在反而觉得的异常太少。在的时候,如果出现异常,可以通过来获取。 作为一名深度 phper,我如果要黑咱们 php,就像说自己母校差一样,大家不要见外。个人博客地址:https://mengkang.net/1368.html 故事的开始 这几天观察错误日志发现有一个数据...

    guqiu 评论0 收藏0

发表评论

0条评论

caozhijian

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<