PHP 反序列化相关

感觉PHP反序列化漏洞经常在一些大佬的博客和CTF题目中看到,而我还没有仔细了解过,实在惭愧。是时候学习一下了。

序列化

先看看PHP中的序列化函数serialize()

serialize ( mixed $value ) : string

传入的参数接受多种类型,返回值为一个字符串。

  • 传入数组

测试代码:

$array = array(1,2,3,4,5);

$s = serialize($array);
var_dump($array);
echo '<br>';
echo $s;

输出:

  • 传入对象

测试代码:

class testClass{
    public $test1 = "abc";
    var $test2 = "def";

    function test3(){
        echo "ghi";
    }

}

$c = new testClass();
$t = serialize($c);

echo $t;

输出:

testClass中的$test1$test2都被序列化了,但是方法test3()不能。序列化只针对变量,不涉及方法。

从上面的测试中,得到了两个字符串:

a:5:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;}
O:9:"testClass":2:{s:5:"test1";s:3:"abc";s:5:"test2";s:3:"def";}

先看第一个a:5:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;}

a表示数组(array),跟在a后面的数字5表示数组的长度。

花括号里面的就是数组的内容。 如果用分号进行分割,每两句就对应数组的一个元素。

例如,i:0;i:1;就是[0] => int(1)。如果数组元素以键值对的形式存在$array = array("test1" => 1,"test2" => 2,);,序列化之后的结果为a:2:{s:5:"test1";i:1;s:5:"test2";i:2;}

第一种类似C语言中的数组,第二种类似Python中的字典。

再来看第二个O:9:"testClass":2:{s:5:"test1";s:3:"abc";s:5:"test2";s:3:"def";}

这个就跟上面的第二种很类似了。

o表示对象(Object),跟在o后面的数字9代表类名的长度。类名后面的数字2表示变量的数量。

s:5:"test1";s:3:"abc"就是public $test1 = "abc";

关于public和var

public和var的作用差不多 因为 var定义的变量如果没有加protected 或 private则默认为public

php4 中一般是用 var
php5 中就一般是用 public了

现在基本都是使用public来代替var
var是定义变量的;而public是定义property(属性)和method(方法)的可见性的

var 是PHP4的时候用的,它和现在的Public作用一样,现在就用Public了,PHP4的时候没有Public,Private,Protected,都只有一个var

有的文章将serialize()json_encode()类比,这样能让人更容易理解序列化。

2020年3月3日更新

<?php
class Test{
    public $test1 = "a";
    private $test2 = "b";
}
$o = new Test();
$str = serialize($o);
echo $str;

输出的序列化字符串为O:4:"Test":2:{s:5:"test1";s:1:"a";s:11:"Testtest2";s:1:"b";}

可以看到,private属性的变量$test2的名称为Testtest2,这和public属性的有区别。

private属性的变量序列化后在名称前加上类的名称。

反序列化

序列化便于储存和传递PHP的值,并保持类型和结构;反序列化是序列化的逆操作,通过序列化字符串得到类型和结构,转换为PHP的值。

反序列化函数为unserialize()

unserialize ( string $str ) : mixed    

传入的参数是一个字符串(序列化字符串),返回的可以有多种类型(对应的类型)。

返回的是转换之后的值,可为 integerfloatstringarrayobject

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

反序列化漏洞

序列化字符串是由一定的规则的。

规则的序列化字符串经过反序列化可以得到预期中的数组,对象...

$b='a:2:{s:5:"test1";s:1:"1";s:5:"test2";s:1:"2";}';

$uns = unserialize($b);

var_dump($uns);

如果序列化字符串不规则呢?

$b='a:2:{s:5:"test1";s:1:"1";s:5:"test2";s:1:"2";}s:5:"test3";s:1:"3"}';

$uns = unserialize($b);

var_dump($uns);

得到的结果和上面的一样。

相比规则的序列化字符串,不规则的序列化字符串后面多了s:5:"test3";s:1:"3"}。PHP 将前面

一部分正确解析了,而后面一部分就弃之不用。

如果将a:2修改为a:1a:3呢?PHP 返回bool(false),反序列化失败,因为字符串不合法,没有一个合法的

序列化字符串。

这就说明一个问题,在反序列化的时候,只要求第一个序列化字符串合法,也就是反序列化时,PHP会从前往后读

取,当读取第一个合法的序列化的字符串时,就会反序列化。

当过滤用户输入参数的时候,如果先序列化再对序列化过后的字符串进行过滤,而且在过滤的过程中会导致原本的

长度改变,就可能造成序列化对象注入漏洞。

参考资料

https://chybeta.github.io/2017/06/17/%E6%B5%85%E8%B0%88php%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/

https://www.freebuf.com/articles/web/167721.html

https://www.cnblogs.com/xu1115/p/10971460.html

https://www.jb51.net/article/180495.htm