🎥反序列化漏洞知识点
其实php的语法到这个时候就已经可以用了。我们这里就直接讲解他的一些知识点了。其实反序列化漏洞能产生的原因也就是因为反序列化的输入是用户可控的,一切输入都是有害的。
最后更新于
其实php的语法到这个时候就已经可以用了。我们这里就直接讲解他的一些知识点了。其实反序列化漏洞能产生的原因也就是因为反序列化的输入是用户可控的,一切输入都是有害的。
最后更新于
用于序列化对象或数组,并返回一个数组。
$value是要序列化的对象或数组。
这里解释一下返回结果的格式有哪些
String
s:size:value
Integer
i:value
Boolen
b:value
Null
N
Array
a:size:{key definition;value definition ;(repeated per element)}
Object
O:strlen(object name):object name :object size:{s:strlen(property name):property name:property definition;(repeated per property)}
接下来我们将特意举一个序列化对象化的例子,让我们看到对象序列化后输出的内容主要是什么。
可以观察到其实是按照下面的格式输出的。
除此之外对于private,public,protected,他们的输出是有区别的,我们可以试试看。
public权限就是没有变化。
属性名变成了%00*%00属性名,我们看到就是加了个星号而已,但其实,我们可以观察到,大小还是增加了两个%00和一个*的大小,就是增加了三个。
属性名变成了%00类名%00属性名,我们看到就是在属性名前面增加了类名。但其实,大小还是增加了类名和两个%00的大小。
将通过 serialize() 函数序列化后的对象或数组进行反序列化,并返回原始的对象结构。
返回的值是转换之后的值,可以是int,float,string,array,或者object都可以。
接下来特别介绍对于对象的执行结果。
返回的就是object(对象)(属性名=>属性内容)
这个我们在php进阶语法的时候·提到过,所以我们直接
serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则 NULL 被序列化,并产生一个 E_NOTICE 级别的错误。
对象被序列化之前触发,返回需要被序列化存储的成员属性,删除不必要的属性。
unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。
预先准备对象资源,返回void,常用于反序列化操作中重新建立数据库连接或执行其他初始化操作。
所以这里直接借用青叶大佬里面的例子进行实验
其实对于结果分析我们就能看到,在调用serialize的时候,自动调用了__sleep,然后因为sleep里面没有准备info,所以后面的输出,其实就没有info了。在调用unserialize的时候,自动调用了__wakeup,里面又重构了了info,所以后面成功输出了info。
__toString()方法用于一个类被当成字符串时应怎样回应。例如echo $obj; 应该是显示些什么。此方法必须返回一个字符串,否则将发出一条E_RECOVERABLE_ERROR级别的致命错误
举个栗子:
很多对这个解释都是说,因为在使用unserilize()之前会使用wakeup(),但是如果序列化字符串中表示对象属性个数的值大于真实的属性个数会跳过__wakeup()的执行。
举个栗子:
正常情况下在这个下面$target的结果会被覆盖为wakeup,但是如果我们test传入的属性个数从1转化为2的时候,$target就不会被覆盖。
其实之前我们有讲过session,但是当时是因为文件包含所以讲解的,这里我们为了反序列化再讲一次。
基础部分我们还是引用之前讲过的。可以从下面的link跳转。
第一次访问网站时,session_start()会创建一个session id,并且通过http的响应头,将这个session id保存到客户端的cookie里面。同时也在服务器端创建一个以session_id命名的文件用于保存用户信息。当再次访问这个网站的时候,会自动通过http头讲cookie保存的session id再携带过来,这时的session id不会修改。
php对session有不同的handler
php
键名+竖线+serialize数据
php_binary
键名的长度对应的ASCII字符+键名+serialize数据
php_serialize
serialize数据
举个例子:
一段代码
php
name|s:7:"evalexp";
php_binary
names:7:"evalexp";
php_serialize
a:1:{s:4:"name";s:7:"evalexp"};
如果开发者在存储Session数据和读取Session数据时所使用的Handler不一致,就将导致无法正确地反序列化,从而导致被反序列化攻击。
借鉴一个例子
如果使用php_serialize进行序列化,得到的session是
如果处理器是php,那么会以竖线为分隔符,就会导致不正确的序列化
当出现首先对对象进行序列化,然后再进行反序列化的时候就会出现字符逃逸。
出现的原因主要是因为,反序列化底层代码是以 ; 作为字段的分隔,以 } 作为结尾,并且是根据长度判断内容的 ,同时反序列化的过程中必须严格按照序列化规则才能成功实现反序列化 ,超出的部分并不会被反序列化成功,这说明反序列化的过程是有一定识别范围的,在这个范围之外的字符都会被忽略,不影响反序列化的正常进行。而且可以看到反序列化字符串都是以";}结束的,那如果把";}添入到需要反序列化的字符串中(除了结尾处),就能让反序列化提前闭合结束,后面的内容就相应的丢弃了。
并且长度不对劲的时候会报错。
这里用先知社区的代码作为例子。
很显然报错了,但是要的就是这个效果。我们还是先解释一下这个代码,我们首先对他进行序列化,这个时候没有问题,然后我们进行字符串替换了,这个时候,bb替换为ccc,这个时候,nameccc是7个字符,但是我们的序列化的结果不能自动更新,所以它还是显示6个字符,这个时候就出问题了。我们再在这个时候落进下石的话,进行反序列化,就会报错。
POP链:
POP(Property-Oriented Programing)面向属性编程,常用于上层语言构造特定调用链的方法,与二进制利用中的ROP(Return-Oriented Programing)面向返回编程的原理相似,都是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链。在控制代码或者程序的执行流程后就能够使用这一组调用链做一些工作了。
所以我们这里先讲解魔法函数
__construct()
创建对象时触发
__destruct()
对象被销毁时触发
__sleep()
在对象被序列化的过程中自动调用,且发生在序列化之前
__wakeup()
该魔术方法在反序列化的时候自动调用,且发生在反序列之前
__get()
用于从不可访问或不存在的属性读取数据
__set()
用于将数据写入不可访问或不存在的属性
__call()
在对象上下文调用不可访问方法时触发
__callStatic()
在静态上下文中调用不可访问的方法时触发
__toString()
在对象当做字符串的时候会被调用
__invoke()
当尝试将对象调用为函数时触发
pop就是利用这些魔法方法一个连着一个。
首先我们知道private和procted在反序列化的时候,protected会变成%00*%00属性名,private会变成%00类名%00属性名。这个%00是url后的结果,没有经过url的结果其实是
这个时候我们可能会发生截断,在这个空格的地方。
为了解决这个问题,我们有两种解决方法。
就是让Payload能够显示这个空格,我们一般会在属性的位置将用大写的S代替小写的s,具体的原因可以看p神的这个例子。
直接将protected和private用public代替,就是换成public,因为php7.1以上的版本对属性类型不敏感。就是直接换成public了,真是直接!