menu Miuranatsudaira
序列化与反序列化学习
321 浏览 | 2019-10-10 | 分类:默认分类 | 标签:

序列化与反序列化

PHP中有两个函数serialize()unserialize()

serialize()
在PHP中创建一个对象,可以通过serialize()把这个对象转变成一个字符串,保存对象的值,方便之后传递和使用。
例子:

<?php
class Neito{
    var $test = 'test';
}

$class1=new Neito;
$class1_ser=serialize($class1);
print_r($class1_ser);

?>

在序列化后得到。

O:5:"Neito":1:{s:4:"test";s:4:"test";}

其中O代表对象,如果你给serialize()传入一个数组就会是字母a
5表示对象的名称有7个字符。
"Neito"表示对象的名称。
1表示有一个值。
{s:4:"test";s:4:"test";}中,
s表示字符串,4表示字符串长度,"test"是字符串的名称。

unserialize()
与serialize()对应,unserialize()可以从之前序列化后的结果中恢复出一个对象。

<?php
class Neito{
    var $test = 'test';
}

$class2='O:5:"Neito":1:{s:4:"test";s:4:"test";}';

$class2_unser=unserialize($class2);
print_r($class2_unser);

?>

得到:

Neito Object ( [test] => test ) 

反序列化漏洞

当传给unserialize()的参数可控时,我们可以构造一个序列化字符串来控制对象内部的变量和函数。
这时要了解PHP的魔术方法,其中魔法函数一般都以__开头,通常会因为某些条件就会自动调用,不需要我们手动。
最常见的几个:

__construct()当一个对象被创建时被调用
__destruct()当一个对象被销毁时调用
__toString()当一个对象被当作一个字符串使用
__sleep()在对象被序列化之前运行
__wakeup()在序列化之后立即被调用

测试一下:

<?php
class Neito{
    var $test = 'test';
    function __wakeup(){
        echo "__wakeup";
        echo "</br>";
    }
    function __construct(){
        echo "__construct";
        echo "</br>";
    }
    function __destruct(){
        echo "__destruct";
        echo "</br>";
    }
}
$class2 = 'O:5:"Neito":1:{s:4:"test";s:4:"test";}';
    print_r($class2);
echo "</br>";
$class2_unser = unserialize($class2);
print_r($class2_unser);
echo "</br>";
?>

得到:

O:5:"Neito":1:{s:4:"test";s:4:"test";}
__wakeup
Neito Object ( [test] => test )
__destruct

不难发现__wakeup和__destruct自动调用了。

例子2
假设一个环境中index.php代码如下:

<?php
class Neito{
    var $test = 'test';
    function __wakeup(){
        $fp = fopen("shell.php","w") ;
        fwrite($fp,$this->test);
        fclose($fp);
    }
}
$class3 = $_GET['test'];
print_r($class3);
echo "</br>";
$class3_unser = unserialize($class3);
require "shell.php";
?>

同目录下有一个comeon.php,
我们可以通过把对象中的test赋值为<?php phpinfo();?>,再调用unserialize()时会通过__wakeup()把test的写入到comeon.php中。
先编写序列化代码:

O:5:"Neito":1:{s:4:"test";s:19:"<?php phpinfo(); ?>";}

测试:

成功。


public、private、protected在序列化字符串时的区别:

先复习一下之前做过的最简单的public,最简单的例子:

<?php
class Neito{
    public $text;

    function execute($payload){
        eval($payload);
    }

    function __destruct(){
        $this->execute($this->text);
    }
}
$a=new Neito();
$a->text='Neito';
echo serialize($a);
?>

序列化后得到内容:
O:5:"Neito":1:{s:4:"text";s:5:"Neito";}
这段反序列化的过程:

<?php
class Neito {
    public $text;

    function execute($payload) {
        eval($payload);
    }

    function __destruct(){
        $this->execute($this->text);
    }
}
unserialize($_GET["a"]);
?>

访问http://localhost:801/Neito/test.php?a=O:5:%22Neito%22:1:{s:4:%22text%22;s:13:%22echo%20%27Neito%27;%22;}
得到返回

Neito

Private类型
把$text成员从public改为private
这里先说一下区别:

public:表示全局,在本类内部、外部类、子类都可以访问

private:表示私有的,只有本类内部可以使用

protected:表示受保护的,只有本类或子类或父类中可以访问

注:实例中不能通过$obj->属性名的方法来调用pravite类型,所以要改:

<?php
class Neito
{
    private $text = "phpinfo()";

    public function setpayload($temp){
        $this->text=$temp;
    }

    function execute($payload){
        eval($payload);
    }

    function __destruct(){
        $this->execute($this->text);
    }
}

$a= new Neito();
$a->setpayload('echo "Neito";');
$data=serialize($a);
echo($data);
file_put_contents("serialize.txt",$data);

?>

序列化后的内容:
O:5:"Neito":1:{s:11:"Neitotext";s:13:"echo "Neito";";}Neito
会发现存在问题:
Neitotext长度为9,但是序列化后发现真实长度为11。

使用winhex把我们生成的serialize.txt文件打开可以发现:

在Neito左右存在两个空字节。
所以在反序列化时,我们输入的必须在类名左右加上%00,否则会因为序列化内容生成到网页后,空字节不会一同生成出去,导致反序列化的时候无法识别是private属性,反序列化会出错。

总结:Private类型在序列化的格式为:%00类名%00


Protected类型
将上列代码改后得到序列化内容:
O:5:"Neito":1:{s:7:"*text";s:13:"echo "Neito";";}Neito
用winhex查看serialize.txt:

可以得出,protected类型在序列化格式为:%00%00类名

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

发表评论

email
web

全部评论 (暂无评论)

info 还没有任何评论,你来说两句呐!