总字符数: 31.62K
代码: 18.75K, 文本: 7.46K
预计阅读时间: 1.90 小时
基础知识
概念
类
对现实生活中一类具有共同特征的事物的抽象(即类可以说成是某一事物的代表,类归纳了事物)
对象
所说的事物就是对象
属性
类中对象所具有的性质
方法
可以用来操作该对象或者该对象可以使用哪些方法
1 |
|
1 |
|
修饰符
Public | Protected | Private | |
---|---|---|---|
本类 | 可以访问 | 可以访问 | 可以访问 |
子类 | 可以访问 | 可以访问 | 不能访问 |
外部 | 可以访问 | 不能访问 | 不能访问 |
1 |
|
魔术方法
__construct()
- 触发时机:当创建对象时,构造函数会被自动调用.
- 作用:初始化对象属性或执行起始化操作.
- 使用要求:可以接收参数,也可以没有参数.
__destruct()
- 触发时机:对象生命周期结束时,如脚本执行结束或对象被销毁.
- 作用:执行清理工作,如释放资源或关闭连接.
- 使用要求:无需返回值,也不接受参数.
__call($name, $arguments)
- 触发时机:在对象中调用一个不可访问方法时,
__call()
会被调用 - 作用:处理对不可访问方法的调用.
- 使用要求:接收方法名和参数数组,通常返回混合类型的结果.
__get($name)
- 触发时机:读取不可访问的属性值时.
__get()
会被调用 - 作用:提供对私有和受保护属性的读取访问.
- 使用要求:接收属性名,返回属性值.
__set($name, $value)
- 触发时机:给不可访问属性赋值时,
__set()
会被调用 - 作用:提供对私有和受保护属性的写入访问.
- 使用要求:接收属性名和属性值.
__unset($name)
- 触发时机:对不可访问属性调用
unset()
时,__unset()
会被调用 - 作用:能够清除私有和受保护属性.
- 使用要求:接收属性名.
__sleep()
- 触发时机:使用
serialize()
序列化对象前. - 作用:指定哪些属性需要被序列化.
- 使用要求:返回一个包含属性名的数组.
__wakeup()
- 触发时机:使用
unserialize()
反序列化对象时,先于对象的其他方法和属性恢复. - 作用:重构对象属性或执行代码,如重建数据库连接等.
- 使用要求:无返回值,通常用于重建资源型属性.
__toString()
- 触发时机:当一个对象需要被当作字符串处理时,如
echo $object;
. - 作用:定义对象的字符串表达形式.
- 使用要求:必须返回一个字符串.
__invoke()
- 触发时机:当尝试将对象当作函数调用时,如
$object();
. - 作用:使对象能以函数的形式被调用.
- 使用要求:可以接收参数,也可以没有参数,且必须返回有效值.
1 | class Demo { |
序列化是什么
序列化是为了保存对象,方便重用
序列化:把对象转换为字节序列的过程称为对象的序列化
1 | string serialize(mixed $value) |
serialize()
函数序列化对象后,可以很方便的将它传递给其他需要他的地方- 如果需要将已序列化的字符串变回PHP的值,可以使用
unserialize()
1 |
|
1 | # 序列化后 |
我们可以发现,把对象序列化之后的数据中并不能看到任何一个方法.
序列化只序列化他的属性,不序列化方法
也就是说我们在利用序列化攻击的时候,也是依托类属性进行攻击.
反序列化
反序列化就是将字符串转换成变量或者对象的过程
1 |
|
与serialize()
对应的unserialize
可以从已存储的表示中创建PHP的值,对于本次的环境而言可以从序列化后的结果中恢复对象(Object
).
1 |
|
在PHP中,对象序列化包含成员变量的可见性处理,不同的可见性修饰符会影响序列化后的字符串格式.
下面是这个过程的简化解释,以及每个可见性修饰符对序列化格式的影响:
Public (公共)成员变量:使用
public
修饰的成员变量在序列化后保留其原始名称和长度.例如,如果$name
是公共变量并且值为"John"
,序列化后会正常显示为"John"
,长度为4.Protected (受保护的)成员变量:使用
protected
修饰的成员变量在序列化时,它的名称前会加上*
字符,并且长度会增加3个字节.所以如果$age
是受保护的变量,序列化后的长度会是原本长度加3.Private (私有的)成员变量:使用
private
修饰的成员变量在序列化时,在变量名前会加上其所在类的名称和两个空字节.因此,如果有一个私有变量$height
在名为object
的类中,序列化后的长度会是变量值的长度加上类名长度再加上2个字节.
在序列化中,\x00
代表空字节,它在私有和受保护成员变量的名称前后用于区分成员变量的作用域.
这里是具体的规则概述:
- Private 成员序列化规则:序列化私有成员时使用的格式为
\x00[类名]\x00[变量名]
. - Protected 成员序列化规则:序列化受保护的成员时使用的格式为
\x00*\x00[变量名]
.
以上规则保证了在序列化和反序列化过程中,成员变量的可见性和归属保持不变,从而维护了对象状态的完整性.
1 | a - array // 数组 |
为什么反序列化?
存储需求
所有PHP里面的值都可以使用函数
serialize()
来返回一个包含字节流的字符串来表示.序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字.在程序执行结束时,内存数据便会立即销毁,变量所储存的数据便是内存数据,而文件、数据库是”持久数据”,因此PHP序列化就是将内存的变量数据”保存”到文件中的持久数据的过程
传输需求
序列化通俗点说就是把一个对象变成可以传输的字符串.
比如:json格式,这就是一种序列化,有可能也是通过array序列化而来的.而反序列化就是把那串可以传输的字符串再变回对象
这样就让对象能够以字节流的形式传输
反序列化漏洞
- 反序列化可能会导致代码被加载和执行
unserialize()
参数可控- php中有可以利用的类并且类中有魔幻函数又称魔术方法
__wakeup()
(CVE-2016-7124)反序列化时,如果表示对象属性个数的值大于真实的属性个数时会导致反序列化失败而同时会跳过
__wakeup()
的执行
- 影响版本
- PHP 5.6.25之前的版本
- 7.x系列中7.0.10之前的版本
1 |
|
正则表达式 '/[oc]:\d+:/i'
各部分的含义如下:
/.../
:正则表达式的界定符,告诉PHP这是一个正则表达式的开始和结束.[oc]
:字符集合,匹配 ‘o’ 或者 ‘c’ 中的任意一个字符.:
:匹配冒号这个字符.\d
:匹配任何数字(digit),等同于 [0-9].+
:量词,表示前面的字符(在这个例子里是 \d,即数字)出现一次或多次.:
:再次匹配冒号这个字符.i
:修饰符,表示匹配时不区分大小写.
所以,这个正则表达式用于查找字符串中的模式,该模式是以 ‘o’ 或 ‘c’(不区分大小写)开头,后跟一个冒号,然后是一个或多个数字,最后以冒号结束.
例如:
o:123:
将会匹配C:456:
也将会匹配x:789:
则不会匹配,因为它不以 ‘o’ 或 ‘c’ 开头.
1 | $flag="ctf{b17bd4c7-34c9-4526-8fa8-a0794a197013}"; |
1 | class Demo{ |
解题思路
关键点:
- 代码提示:需要读取 fl4g.php,但是直接访问会被重定向至 index.php.
- 利用点:
__destruct()
方法中的highlight_file($this->file, true)
可以读取文件内容. - 目标:在反序列化后调用
__destruct()
,同时避免__wakeup()
将$file
重置为 index.php.
优化解题思路:
- 利用 CVE-2016-7124 漏洞:序列化字符串的属性个数大于实际数量时,可以跳过
__wakeup()
. - 绕过技巧:
- 改变序列化内容中的属性个数,从而跳过
__wakeup()
.
- 改变序列化内容中的属性个数,从而跳过
操作步骤:
- 将序列化的属性个数从 1 改为 2.
- 使用
base64
编码处理序列化内容. - 提交修改后的数据,触发
__destruct()
,读取 fl4g.php.
利用一般都是基于”自动调用”的魔术方法,当漏洞/危险代码存在类的普通方法中,就不能指望通过”自动调用”来达到目的.这时的利用方法如下
- 寻找相同的函数名
- 把敏感函数和类联系在一起
反序列化Bypass
php7.1+反序列化对类属性不敏感
在PHP 7.1版本及以后,protected属性在序列化时不再需要特殊前缀.这意味着,即便序列化字符串中缺少\x00*\x00
,PHP依然能够正确处理受保护的属性值.
php5.5.9
1 |
|
1 |
|
php7.1.9
1 |
|
绕过__wakeup(CVE-2016-7124)
版本:
PHP5 < 5.6.25
PHP7 < 7.0.10
利用方式:序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行
对于下面这样一个自定义类
1 |
|
如果执行unserialize('O:4:"test":1:{s:1:"a";s:3:"abc";}');
输出结果为666
而把对象属性个数的值增大执行unserialize('O:4:"test":2:{s:1:"a";s:3:"abc";}');
输出结果为abc
绕过部分正则
使用 preg_match('/^O:\d+/')
可以检查一个字符串是否以PHP对象的序列化格式开头.这个技巧在一些安全竞赛(CTF)中很实用.
- 一个很简单的绕过手法就是在数字前面加一个
+
,这样就饶过了正则,因为正常来说整数会省略+
,但是加上也不算错.当你在URL中传递参数时,需要记住将加号(+
)编码为%2B
- 使用
serialize(array($a));
可以序列化一个包含变量$a
的数组,这样即使\$a
是一个对象,它的序列化字符串也会以字母a
开始,而不是对象表示的O
,这是一个避免某些安全问题的小技巧.
1 |
|
除了加号(+
),可能还有其他字符或者序列化格式的特定特征可以用于绕过检查,例如:
空白字符:在某些情况下,正则表达式可能没有考虑空白字符(如空格、制表符或换行符),可能会忽略它们.
- 空格字符 (
- 制表符 (
\t
) - 换行符 (
\n
) - 回车符 (
\r
)
如果正则表达式仅检查开始的序列化对象而没有考虑对象内部的空白,那么可以在对象的属性值中插入空白字符,例如在s:3:"abc"
后添加一个空格或其他空白字符.
但是,需要注意的是,PHP的unserialize()
函数通常不会忽略序列化字符串中的空白字符,除非它们是字符串值的一部分.这意味着,虽然你可能绕过了正则表达式的检测,但是修改后的序列化字符串可能不会成功被unserialize()
函数反序列化,除非这些空白字符位于序列化数据的非关键部分.
16进制绕过字符的过滤
1 | O:4:"test":1:{s:8:"username";s:5:"admin";} |
1 | // 定义一个名为 test 的类 |
PHP反序列化字符逃逸
此类题目的本质就是改变序列化字符串的长度,导致反序列化漏洞
这种题目有两个共同点:
- PHP序列化的字符串经过了替换或者修改,导致字符串长度发生变化
- 总是先进行序列化,再进行替换修改操作
分类:
- 替换后字符变多
- 替换后字符变少
情况1:过滤后字符变多
1 |
|
正常情况,传入name=cat
如果此时多传入一个x的话会怎样,毫无疑问反序列化失败,由于溢出(s本来是4结果多了一个字符出来),我们可以利用这一点实现字符串逃逸
首先来看看结果,再来讲解
我们通过GET参数传入了一个包含多个x
字符的name
值:
1 | $name = "catxxxxxxxxxxxxxxxxxxxx";i:1;s:6:"woaini";}"; |
当使用以下代码对序列化的字符串进行替换操作时,问题就出现了:
1 | // 把字符串中的每一个'x'替换成两个'xx' |
这里发生了什么:
- 原始输入:
name=catxxxxxxxxxxxxxxxxxxxx";i:1;s:6:"woaini";}
- 字符计数:原始输入的
";i:1;s:6:"woaini";}
部分共有20个字符. - 替换效果:每个
x
都被替换为xx
,所以原本的20个x
变成了40个. - 字符串逃逸:由于替换后
x
的数量翻倍,导致原始的结尾部分";i:1;s:6:"woaini";}
被覆盖,使得序列化字符串的格式被破坏. - 反序列化结果:最终的
"
字符闭合了字符串,使得woaini
可以成功被反序列化出来. - 结尾处理:剩余的结尾分号
";}
正确闭合了整个序列化过程,使得原来应该出现在结尾的";i:1;s:7:"I am 11";}"
被忽略,不影响反序列化结果.
情况2:过滤后字符变少
老规矩先上代码,就是把反序列化后的两个x替换成为一个
1 |
|
正常情况传入name=mao&age=11
的结果
最后构造的结果:
1 | name=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&age=11";s:3:"age";s:6:"woaini";} |
在序列化字符串中,由于我们插入了40个x
字符,这导致原本的序列化数据因长度不匹配而被”截断”.这意味着序列化字符串中接下来的20个字符–";s:3:"age";s:28:"11
–被这些x
字符取代了.因为字符串在某个点被"
字符闭合,这使得序列化的数据结构被破坏,从而允许插入额外的数据.
具体来说:
1 | s:3:"age";s:28:"11";s:3:"age";s:6:"woaini";}" |
在这个序列化字符串中:
s:3:"age";
指定了一个长度为3的字符串”age”.s:28:"11"
试图定义一个长度为28的字符串,但实际上只包含”11”.- 多余的
x
字符替换了这个字符串后面应有的内容. - 最终
"
字符闭合了字符串,使得原本应当由序列化数据决定的内容现在可以被外部输入覆盖. - 结果是,”age”的值不再是原本的”11”,而是被篡改后的”woaini”.
POP链
POP链就是利用了PHP中对象的自动调用魔术方法特性,将多个类和方法串联起来,形成一个链式调用.当PHP反序列化时,会自动调用这些方法,触发代码执行.
POP链构造技巧
- 简单浏览:找出可能的漏洞点
- 多去注意一些容易触发漏洞的函数:
eval
、include
等
- 多去注意一些容易触发漏洞的函数:
- 根据漏洞点反推:看逻辑是否可行(参数是否可控、魔术方法是否能触发、条件是否可达成等)
- 一般是先找注入点,判断注入需要的参数,然后找到包含执行注入的函数(一般就是魔术方法),再找执行此函数的条件a,判断条件a是否可以满足,然后再找执行条件a需要满足的条件b,依次找下去直到不需要再找需要满足的条件即可.
- 最后构造POC验证
- 构造的时候根据上一步找到的条件,最好从后往前构造,并且要找正确触发魔术方法的究竟是谁($this指的是谁)
一道简单的pop链例题
1 | // 定义了一个名为test的类 |
- 漏洞利用点:
eval($this->test);
使hello()可执行,利用eval执行系统命令:$test=system(dir); - hello()执行需满足: __destruct可触发:(反序列化test对象之后自动执行)且将$index=new execute()
- 将index设置为execute的示例需满足:__construct 不触发(反序列化test对象不会触发)
简单的POC
1 |
|
因有privat修饰符(会产生不可打印字符)所以我们后面使用urlencode
输出以便我们复制
CTF例题
1 | class Modifier { |
- 漏洞构造点:让append()执行利用文件包含include打开flag文件 让$value=flag.php
- append()执行 需要:invoke()被触发(Modifierl)实例作为函数调用)
- Modifierl实例作为函数调用需要:__get被触发)(访问Test对象不存在的属性)且$p=new Modifier()触发`__invoke()`
- 访问Test对象不存在的属性:__toString()触发(Show对象被当作字符串)且$str=new Test();触发`__get()`
- Show对象被当作字符串:__wakeup()触发(对show对象反序列化自动触发)且$source = new Show();触发`__toString`
最后顺序要求:触发__wakeup
–>触发__tostring
->触发__get
–>触发__invoke
POC
1 | class Modifier{ |
资料参考自
POP链与Gadget的区别
PHP的POP链(Property Oriented Programming chain)和Java反序列化中的Gadget概念都是与对象序列化和反序列化安全相关的术语,但它们属于不同的编程语言环境,并且在具体的攻击方式和安全影响上有所区别.
PHP的POP链:
- 在PHP中,POP链指的是通过反序列化过程中魔术方法的调用链,这些魔术方法可能包括
__wakeup()
,__destruct()
,__toString()
,__call()
等. - 通过精心设计的反序列化字符串,攻击者可能会触发这些魔术方法的连锁执行,从而可能导致代码执行、文件操作、数据库操作等潜在的危险行为.
- PHP的POP链攻击通常依赖于对象的内部状态和方法的定义来影响应用程序的行为.
- 在PHP中,POP链指的是通过反序列化过程中魔术方法的调用链,这些魔术方法可能包括
Java的Gadget:
- Java中的Gadget通常指的是在反序列化过程中可以被调用以执行某些操作的对象和方法组合.
- Java反序列化时,JVM会根据序列化数据中的描述来重建对象图,并在这个过程中可能会调用存在于序列化对象中的特殊方法,如
readObject()
,readResolve()
,validateObject()
,readExternal()
等. - 攻击者可以利用这些特殊方法来执行恶意行为,如远程代码执行.被恶意利用的这些类和方法组合被称为Gadget.
区别:
- 语言环境:POP链是属于PHP的安全问题,而Gadget是Java的概念.
- 攻击链:PHP的POP链是利用魔术方法的特性来形成的攻击链,而Java的Gadget通常是利用JVM在反序列化过程中调用特定方法的行为来形成攻击向量.
- 防御措施:虽然两者都需要谨慎处理用户的输入以及序列化和反序列化的数据,但具体的防御措施会有所不同.例如,Java中可能会使用
lookAheadInput
和validatingObjectInputStream
,而PHP中则需要控制魔术方法的使用和访问权限,或者避免使用序列化存储用户输入的数据. - 危险方法:在PHP中主要是魔术方法,而在Java中则是实现了序列化接口的类中的特殊方法.
Phar反序列化
关于Phar反序列化漏洞,它发生的原因通常是因为Phar文件在被访问时,其元数据(metadata)会被自动反序列化.如果攻击者可以控制Phar文件的内容或元数据,他们可能会利用这个反序列化过程来执行恶意代码.
Phar文件的元数据是在创建Phar时通过
Phar::setMetadata()
设置的,当Phar文件被访问时,比如通过phar://
流封装协议,Phar文件内的元数据会被反序列化.如果元数据包含了恶意的序列化对象,而这个对象在反序列化时会调用可被利用的魔术方法(如__wakeup
、__destruct
、__toString
),那么就可能触发一个漏洞.
什么是Phar
PHAR(“Php ARchive”)是PHP类似于JAR的一种打包文件,在PHP 5.3或更高版本中默认开启,这个特性使得PHP也可以像Java一样方便地实现应用程序打包和组件化
一个应用程序可以打成一个Phar包,直接放到PHP-FPM中运行
Phar文件结构
Phar文件是PHP中用于分发或部署整个PHP应用的一种方式,它可以包含必要的所有文件,如PHP代码、HTML模板、图像等.Phar文件的结构使得它可以在PHP中自包含,并且通常带有签名以验证其完整性.
以下是Phar文件的组成部分的详细说明:
Stub:
Stub是Phar文件的启动器(bootstrap)部分,它是一个PHP脚本,在Phar被执行时首先被运行.Stub通常用来设置Phar的运行环境或包含自动加载器.
Stub的基本要求是它必须以特殊的
__HALT_COMPILER();
函数调用结束.这个函数会停止编译器的执行,从而允许将非PHP代码包含在同一个文件中.Stub的结构通常是这样的:
1
__HALT_COMPILER();
之后的数据可以是Phar文件的其它部分,但这个
__HALT_COMPILER();
之后的内容对PHP来说是不可执行的.
Manifest:
- Manifest描述了Phar文件中包含的所有文件和元数据.
- 其中每个文件都会有一个相应的条目,条目中包含了文件的名称、大小、时间戳、压缩类型等信息.
- Meta-data部分存储了关于Phar自身的信息,以序列化的形式存储.如果存在反序列化漏洞,攻击者可以通过精心构造的meta-data来执行恶意代码.
File Contents:
- 这部分包含了Phar文件中所有文件的实际内容.
- 文件内容可以是压缩的也可以是未压缩的,这取决于在创建Phar时的设置.
Signature:
- 签名是用来验证Phar文件完整性的.
- Phar文件可以使用不同类型的签名,如SHA1或SHA256,以确保文件自创建以来没有被修改.
- 签名通常位于Phar文件的最后部分,Phar文件在被使用前会校验这个签名以确保安全.
当Phar文件被包含或直接执行时,PHP会按照这些部分的顺序来处理Phar文件.如果启用了Phar扩展,并设置了允许Phar文件的执行,PHP将按照Manifest中的信息加载文件,并执行Stub中的代码.如果Phar文件的签名验证失败,或者文件不完整,那么PHP将不会执行Phar文件.
PHP内置了一个Phar类来处理相关操作
必须将PHP.INI中的phar.readonly选项设置为Off,否则无法生成Phar文件
1 |
|
我们用010将Phar打开,观察一下数据
可以明显的看到meta-data是以序列化的形式存储的.
有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过phar://伪协议
解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下:
受影响的函数列表 | |||
---|---|---|---|
fileatime |
filectime |
file_exists |
file_get_contents |
file_put_contents |
file |
filegroup |
fopen |
fileinode |
filemtime |
fileowner |
fileperms |
is_dir |
is_executable |
is_file |
is_link |
is_readable |
is_writable |
is_writeable |
parse_ini_file |
copy |
unlink |
stat |
readfile |
1 |
|
可以看到已经成功的触发了反序列化
问题?
- 序列化:当您调用
$phar->setMetadata($o);
时,PHP将您的$o
对象及其所有属性(在这个例子中是data
)序列化为一个字符串,并将这个字符串存储在Phar文件的元数据中.这个字符串包含了重建对象$o
时所需要的所有信息. - 反序列化:当您包含
phar://phar.phar
文件时,PHP Phar扩展会自动尝试反序列化存储在其中的所有元数据.这意味着,Phar文件中存储的序列化字符串会被转换回PHP对象.
现在,理解上述过程后,让我们来看看您的序列化和反序列化代码:
- 在序列化代码中,
$o
是TestObject
的一个实例,并且它有一个名为data
的属性,这个属性被设置为字符串'It\'s Bob'
. - 在反序列化的脚本中,
TestObject
类被定义了一个析构函数__destruct()
,它会在对象被销毁时自动调用,并输出data
属性.
当您在第二个脚本中包含Phar文件时,Phar扩展会自动反序列化元数据,并创建一个新的 TestObject
对象.由于反序列化创建的对象具有与序列化期间相同的属性和值,它的 data
属性将包含 'It\'s Bob'
.
当脚本执行结束或者没有其他引用指向该对象时,新创建的 TestObject
对象会被销毁.对象被销毁时,它的 __destruct()
方法被调用,然后 echo $this->data;
语句执行,输出存储在 data
属性中的字符串.
这是两个完全独立的 TestObject
对象实例:
- 一个是在序列化时创建并存储在Phar文件中的;
- 一个是在反序列化时由Phar扩展自动创建的
反序列化的对象实例具有序列化对象实例相同的属性值.这就是为什么它能够输出序列化时设置的字符串的原因.
漏洞复现
- 环境准备
upload_file.php
文件上传表单及后端检测上传的文件类型是否为gif,后缀名是否为giffile_vuln.php
存在file_exists(),并且存在__destruct()
- 利用条件
- phar文件要能够上传到服务器端
- 服务端需要有
file_exists()
、fopen()
、file_get_contents()
、file()
等文件操作的函数 - 要有可用的魔术方法作为”跳板”
- 文件操作函数的参数可控,且:
/
,phar
等关键字没有被过滤
1 |
|
1 |
|
先用payload生成一个phar.phar
1 |
|
将phar后缀名改为gif,然后通过文件上传传上去
通过file_vuln页面利用phar伪协议包含phar.gif造成反序列化漏洞
Phar反序列化的绕过
压缩过滤器触发phar时解决phar://不能出现在首部的问题
这时我们可以利用
compress.zlib://
或compress.bzip2://
函数
compress.zlib://
或compress.bzip2://
同样适用于phar://
payload:
compress.zlib://phar://phar.phar/test.txt
1 | compress.bzip://phar:///test.phar/test.txt |
PHP-Session反序化
资料参考自
PHP在session存储和读取时,都会有一个序列化和反序列化的过程,PHP内置了多种处理器用于存取
$_SESSION
数据,都会对数据进行序列化和反序列化,PHP中的Session的实现是没有的问题的,漏洞主要是由于使用不同的引擎来处理session文件造成的
存在对$_SESSION变量赋值
php引擎存储Session的格式为
php | 键名 + 竖线 + 经过 serialize() 函数序列处理的值 |
---|---|
php_serialize | (PHP>5.5.4) 经过 serialize() 函数序列化处理的数组 |
如果程序使用两个引擎来分别处理的话就会出现问题。比如下面的例子,先使用php_serialize
引擎来存储Session
.
1 |
|
接下来使用php引擎来读取Session文件
1 |
|
漏洞的主要原因在于不同的引擎对于竖杠|
的解析产生歧义。
对于php_serialize引擎来说|
可能只是一个正常的字符;但对于php引擎来说|
就是分隔符,前面是$_SESSION['username']
的键名 ,后面是GET参数经过serialize
序列化后的值。从而在解析的时候造成了歧义,导致其在解析Session
文件时直接对|
后的值进行反序列化处理。
可能有的人看到这里会有疑问,在使用php引擎读取Session文件时,为什么会自动对|
后面的内容进行反序列化呢?也没看到反序列化unserialize
函数。
这是因为使用了session_start()
这个函数 ,看一下官方说明
可以看到PHP能自动反序列化数据的前提是,现有的会话数据是以特殊的序列化格式存储。
明白了漏洞的原理,也了解了反序列化漏洞的位置,现在来思考一下攻击思路
首先访问
sess_write.php
,在传入的参数最开始加一个|
,由于sess_write.php
是使用php_serialize
引擎处理,因此只会把|
当做一个正常的字符,然后访问sess_read.php
,由于用的是php引擎,因此遇到|
时会将其看做键名与值的分隔符,从而造成歧义,导致其在解析session
文件时直接对|
后的值进行反序列化处理
1 | class student |
攻击思路中说到了因为不同的引擎会对|
,产生歧义,所以传参的时在payload前加个|
,作为a参数
1 | error_reporting(0); |
1 | error_reporting(0); |
PHP原生类的利用
未完待续qaq