序列化和反序列化先容

serialize()将一个工具转换成一个字符串,unserialize()将字符串还原为一个工具,在PHP运用中,序列化和反序列化一样平常用做缓存,比如session缓存,cookie等。
大略点讲序列化便是把一个工具变为可以传输的字符串,而反序列化便是把字符换换原为工具。

大略例子

php数组反序PHP反序列化常识点总结 Python

<?phpclass test{public $suifeng=&#34;shuai";}$a=new test(); //实例化一个工具$b=serialize($a); //进行序列化echo $b; //输出序列化后的字符串echo '<br>';echo "我是分割线";$c=unserialize($b); //把序列化后的字符串反序列化echo '<br>';echo $c->suifeng;?>

这里我们再来看看反序列化后输出字符的含义

首先输出的内容为

O:4:"test":1:{s:7:"suifeng";s:5:"shuai";}

O->object4->object的长度test->object的名称1->object中变量个数s->变量名数据类型7->变量名长度suifeng->变量名S->变量值数据类型5->变量值长度shuai->变量的值

PHP其他数据类型

a - arrayb - booleand - doublei - integero - common objectr - references - stringC - custom objectO - classN - nullR - pointer referenceU - unicode string

PHP常见邪术函数

__construct() //一个工具创建时被调用

__destruct() //一个工具销毁前被调用

__call() //调用类不存在的方法时实行

__callStatic() //调用类不存在的静态办法方法时实行。

__wakeup() //将在反序列化之后立即被调用

__sleep() //在工具被序列化前被调用

__toString() //当一个工具被当做字符串利用时被调用

__get() //用于从不可访问的属性读取数据

__set() //用于将数据写入不可访问属性

__invoke() //调用函数的办法调用一个工具时的回应方法

__isset() //在不可访的属性上调用isset()或empty()触发

__unset() //在不可访的属性上利用unset()时触发

public、protected、private下序列化工具差异

php v7.x反序列化的时候对访问种别不敏感

public变量直接变量名反序列化出来

protected变量\x00 + + \x00 + 变量名可以用S:5:"\00\00op"来代替s:5:"??op"

private变量\x00 + 类名 + \x00 + 变量名

反序列化漏洞形成条件

1、unserialize函数的参数可控

2、后台利用了相应的PHP中的邪术函数

反序列化漏洞事理

我们先运行如下一串代码

<?phpclass ABC{function __construct(){echo '调用告终构函数<br>';}function __destruct(){echo '调用了析构函数<br>';}function __wakeup(){echo '调用了清醒函数<br>';}}echo '创建工具a<br>';$a=new ABC;echo '序列化<br>';$a_ser=serialize($a);echo '反序列化<br>';$a_unser=unserialize($a_ser);echo '工具快去世了!
';?>

PHP措辞本身漏洞

还有一种PHP措辞本身漏洞碰到某种特点情形导致的反序列化漏洞

如:__wakeup失落效引发(CVE-2016-7124)

php版本< 5.6.25 | < 7.0.10

当序列化字符串中,如果表示工具属性个数的值大于真实的属性个数时就会跳过__wakeup()的实行

PHP_session序列化问题

当session_start()被调用或者php.ini中session.auto_start为1时,PHP内部调用会话管理器,访问用户session被序列化往后,存储到指定目录(默认为/tmp)。

PHP中有三种序列化处理器,如下:

处理器

对应的存储格式

php

键名 + 竖线 + 经由serialize()函数反序列化处理的值

php_binary

键名的长度对应的ASCII字符 + 键名 + 经由serialize()函数反序列化处理的值

php_serialize(php>=5.5.4)

经由serialize()函数反序列处理的数组

配置文件php.ini中含有这几个与session存储配置干系的配置项:

session.save_path="" --设置session的存储路径,默认在/tmpsession.auto_start --指定会话模块是否在要求开始时启动一个会话,默认为0不启动session.serialize_handler --定义用来序列化/反序列化的处理器名字。
默认利用php

session.save_handler="" --设定用户自定义存储函数,如果想利用PHP内置会话存储机制之外的可以利用本函数(数据库等办法),比如files便是session默认以文件的办法进行存储

且在PHP中默认利用的是PHP引擎,如果想要修正成其他引擎,我们须要添加代码ini_set('session.serialize_handler', '须要设置的引擎'),例:

<?php

ini_set('session.serialize_handler', 'php_serialize');

session_start();

存储的文件因此sess_sessionid来进行命名的,文件的内容便是session值的序列话之后的内容,例如session文件名称为:sess_1ja9n59ssk975tff3r0b2sojd5

如果PHP在反序列化存储的$_SEESION数据时的利用的处理器和序列化时利用的处理器不同,会导致数据无法精确反序列化,通过分外的假造,乃至可以假造任意数据。

PHP反序列化可以利用的原生类

__call

SoapClient

这个也算是目前被挖掘出来最好用的一个内置类,php5、7都存在此类。

SSRF

<?php$a = new SoapClient(null,array('uri'=>'http://example.com:5555', 'location'=>'http://example.com:5555/aaa'));$b = serialize($a);echo $b;$c = unserialize($b);$c->a();

__toString

Error

适用于php7版本

XSS

开启报错的情形下:

<?php$a = new Error("<script>alert(1)</script>");$b = serialize($a);echo urlencode($b);

//Test$t = urldecode('O%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A25%3A%22%3Cscript%3Ealert%281%29%3C%2Fscript%3E%22%3Bs%3A13%3A%22%00Error%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A18%3A%22%2Fusercode%2Ffile.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7D');$c = unserialize($t);echo $c;

Exception

适用于php5、7版本

XSS

开启报错的情形下:

<?php$a = new Exception("<script>alert(1)</script>");$b = serialize($a);echo urlencode($b);

//Test$c = urldecode('O%3A9%3A%22Exception%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A25%3A%22%3Cscript%3Ealert%281%29%3C%2Fscript%3E%22%3Bs%3A17%3A%22%00Exception%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A18%3A%22%2Fusercode%2Ffile.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A16%3A%22%00Exception%00trace%22%3Ba%3A0%3A%7B%7Ds%3A19%3A%22%00Exception%00previous%22%3BN%3B%7D');echo unserialize($c);

phar://协议

观点

一个php运用程序每每是由多个文件构成的,如果能把他们集中为一个文件来分发和运行是很方便的,这样的列子有很多,比如在window操作系统上面的安装程序、一个jquery库等等,为了做到这点php采取了phar文档文件格式,这个观点源自java的jar,但是在设计时紧张针对 PHP 的 Web 环境,与 JAR 归档不同的是Phar归档可由 PHP 本身处理,因此不须要利用额外的工具来创建或利用,利用php脚本就能创建或提取它。
phar是一个合成词,由PHP和 Archive构成,可以看出它是php归档文件的意思(大略来说phar便是php压缩文档,不经由解压就能被 php 访问并实行)

phar组成构造

stub:它是phar的文件标识,格式为xxx<?php xxx; __HALT_COMPILER();?>;manifest:也便是meta-data,压缩文件的属性等信息,以序列化存储contents:压缩文件的内容signature:署名,放在文件末端

这里有两个关键点,一是文件标识,必须以__HALT_COMPILER();?>结尾,但前面的内容没有限定,也便是说我们可以轻易假造一个图片文件或者其它文件来绕过一些上传限定;二是反序列化,phar存储的meta-data信息以序列化办法存储,当文件操作函数通过phar://伪协议解析phar文件时就会将数据反序列化,而这样的文件操作函数有很多

条件条件

php.ini中设置为phar.readonly=Offphp version>=5.3.0

demo测试

根据文件构造我们来自己构建一个phar文件,php内置了一个Phar类来处理干系操作

<?phpclass TestObject {}@unlink("phar.phar");$phar = new Phar("phar.phar"); //后缀名必须为phar$phar->startBuffering();$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub$o = new TestObject();$phar->setMetadata($o); //将自定义的meta-data存入manifest$phar->addFromString("test.txt", "test"); //添加要压缩的文件//署名自动打算$phar->stopBuffering();?>

可以很明显看到manifest因此序列化形式存储的

如果现在通过phar://包装器对我们现有的Phar文件实行文件操作,则其序列化元数据将被反序列化。
这意味着我们在元数据中注入的工具被加载到运用程序的范围中。
如果此运用程序具有已命名的类AnyClass并且具有魔术方法__destruct()或已__wakeup()定义,则会自动调用这些方法。
这意味着我们可以在代码库中触发任何析构函数或唤醒方法。
更糟糕的是,如果这些方法对我们注入的数据进行操作,那么这可能会导致进一步的漏洞。

以下是受影响函数列表

这时利用phar://协议即可

利用条件

phar 文件能够上传

文件操作函数参数可控, : ,/ phar 等分外字符没有被过滤

有可用的魔术方法作为"跳板"

反序列化字符逃逸

PHP 在反序列化时,底层代码因此 ; 作为字段的分隔,以 } 作为结尾(字符串除外),并且是根据长度判断内容的,同时反序列化的过程中必须严格按照序列化规则才能成功实现反序列化 。

下面我们来剖析一段代码

<?phpclass test{public $suifeng="shuai";}$a=new test();$b=serialize($a);echo $b;$c=unserialize($b);echo '<br>';echo $c->suifeng;?>

创造序列化为 O:4:"test":1:{s:7:"suifeng";s:5:"shuai";},也正常进行了反序列化。
当我们把序列化结果修正为O:4:"test":1:{s:7:"suifeng";s:5:"shuai";}i:1;s:4:"test"; 后创造还是会正常解析。

但是我们修正其长度就会报错,如 O:4:"test":1:{s:7:"suifeng";s:4:"shuai";}

知道这个特性我们再来剖析如下一段代码

<?phpfunction filter($string){return str_replace('test','test1',$string);}$username = "admin";$password = "1234";$user = array($username, $password);var_dump(serialize($user));echo '\n';$r = filter(serialize($user));var_dump($r);echo '\n';var_dump(unserialize($r));

可以看到反序列化为了a:2:{i:0;s:5:"admin";i:1;s:4:"1234";},当我们把username参数修正为admintest时,后续代码流程先反序列化$user,然后再实行Filter函数里面的str_place函数,把test更换成了test1,这样就导致了长度不一致,终极导致反序列化失落败。

a:2:{i:0;s:9:"admintest";i:1;s:4:"1234";}

a:2:{i:0;s:9:"admintest1";i:1;s:4:"1234";}

假设这个代码流程是一个创建账号的代码流程,此时$username可由用户可掌握,这时我们就可以通过掌握可控参数导致反序列化字符逃逸。
实在质实在也是和sql注入一样,对双引号,大括号的闭合,只不过反序列化字符逃逸须要知足一些其特点的条件。
接下来我们就对其进行布局payload。

由于其严格按照以 ; 作为字段的分隔,以 } 作为结尾(字符串除外),以是我们可以这么对其进行闭合。

可以看到我们布局$username=admintest";i:1;s:6:"123456";},经由序列化后序列化为了a:2:{i:0;s:29:"admintest";i:1;s:6:"123456";}";i:1;s:4:"1234";},这样的序列化我们再对其进行反序列化赤色部分就不会进入到反序列化中。
但是可以看到更换后还是没有反序列化成功,我们来剖析一下。

更换后我们得到

a:2:{i:0;s:29:"admintest1";i:1;s:6:"123456";}";i:1;s:4:"1234";},赤色部分在进行反序列化的时候会被进行忽略,那进行反序列化的字段就为

a:2:{i:0;s:29:"admintest1";i:1;s:6:"123456";}

可以看到这里的s:29:"admintest1"明显不对,以是我们的反序列化会失落败,那么怎么去让这里保持精确呢,这也便是我们反序列化字符字符逃逸特点条件须要考虑的东西。

在str_replace('test','test1',$string)代码里,test更换为了test1,test1比较之前test多了一个字符,以是只要我们再加上只够的test让其更换成test1,且让长度相等,这样就可以让我们的反序列化正常进行了。

我们布局payload

admintesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest";i:1;s:6:"123456";}

这样我们就对改业务的密码进行了修正