PHP反序列化
PHP反序列化
序列化
- 序列化的作用
将对象或者数组转化为可存储/传输的字符串
- 对象序列化
\x00为空字符,一个空字符长度为 1
反序列化的特性
- 反序列化之后的内容为一个对象
- 反序列化生成的对象里的值,由反序列化里的值(字符串$a)提供;与原有预定的值无关
- 反序列化不触发类的成员方法;需要调用方法后才能触发
反序列化的作用
将序列化后的参数还原成实例化的对象
- 反序列化的过程就是碰到
;}
与最前面的{
配对后,便停止反序列化,后面的数据会直接丢弃。 - 反序列化的过程会根据
s
所指定的字符长度
去读取后边的字符。如果指定的长度错误则反序列化就会失败。 - 类中不存在的属性也会进行反序列化。
反序列化漏洞
反序列化漏洞成因:
反序列化过程中,unserialize()接收的值(字符串)可控;通过更改这个值(字符串),得到所需要的代码,即生成的对象的属性值;通过调用方法,触发代码执行
魔术方法
什么是魔术方法
一个预定义好的,在特定情况下自动触发的行为方法。
魔术方法的作用
在特定条件下自动调用相关方法,最终导致触发代码
pop链
POP链就是利用魔法方法在里面进行多次跳转然后获取敏感数据的一种payload
POC编写
POC(全程:Proof of concept)中文译作概念验证。POC是一段不完整的程序,仅仅是为了证明提出者的观点的一段代码。
字符逃逸
反序列化分隔符
反序列化以
;
结束,后面的字符串不影响正常的反序列化
属性逃逸
一般在数据先经过一次serialize再经过unserialize,在这个中间反序列化的字符串变多或者变少的时候有才可能存在反序列化属性逃逸
字符变多
- 看过滤,判断字符变多还是字符变少,计算变化个数
- 一个字符,构造过滤字符的个数为构造的字符长度
- n个字符,构造过滤字符的个数为构造的字符长度/n
这段代码中 $u
必须包含 admin
,然后把 admin
替换为 hacker
其次通过判断 password
是否等于 8888888
来判断是否输出 flag
先给 username
赋值 admin
,然后把 password
改为 88888888
,观察一下返回的数据
经过替换后 admin
变成了 hacker
,多出来了一个字符,但标记长度没有变化,还是 s:5
,造成了实际长度大于标记长度的情况,从而反序列化失败。
同时我们发现后面我们需要构造的字符 ";s:8:"password";s:8:"88888888";}
长度为 33
,由于过滤规则每次替换增加 1
个字符,所以我们需要 33
个 admin
得到如下字符串,
我们发现 hacker
正好是 198
个字符,而 password
也变成了我们想要的 88888888
字符变少
- 构造想要的值正常序列化,拿到最终的逃逸字符
- 逃逸字符前任意字符+双引号闭合,传入要控制的值
- 根据需要逃逸的字符串的长度,传入对应的过滤字符
思路同上,先输出一下 serialize
后的数据
发现 admin
变成了 hack
,但是标记长度没有变化,还是 s:4
,造成了实际长度小于标记长度的情况,我们每增加一个 admin
匹配替换后就减少 1
个字符,我们要做的就是让他往后去吞噬一些我们构造的代码,这样就可以构造出我们想要的代码了。
这样是我们想要构造的代码
我们把它传入 password
中观察返回数据
得到如下字符串
所以我们需要吞噬的字符如下
由于每次匹配替换只会减少一个字符,所以我们需要构造一个长度为 23
的字符串,这样就可以吞噬到我们想要的代码了。
得到如下字符串
session反序列化
PHP在 session
存储和读取时,都会有一个序列化和反序列化的过程,PHP内置了多种处理器用于存取 $_SESSION
数据,都会对数据进行序列化和反序列化
在 php.ini
中通常存在以下配置项:
session.save_path
设置session的存储路径session.save_handler
设定用户自定义存储函数session.auto_start
指定会话模块是否在请求开始时启动一个会话session.serialize_handler
定义用来序列化/反序列化的处理器名字。默认使用php
不同的引擎所对应的 session
的存储方式不相同。
引擎 | 存储方式 | 示例 |
---|---|---|
php | 键名 + 竖线 + 经过 serialize() 函数序列化处理的值 |
name |
php_binary | 键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数序列化处理的值 |
names:7:"iami233"; |
php_serialize | 经过 serialize() 函数序列化处理的数组 |
a:1:{s:4:"name";s:7:"iami233";} |
举个例子
我们新建一个 1.php
文件,使用 php_serialize
引擎
访问 localhost/1.php
后生成的 session
文件内容文件为:
再新建一个 2.php
文件,不声明引擎的话,默认是 php
此时访问 localhost/2.php
即可执行 phpinfo()
函数
phar反序列化漏洞
phar是PHP类似于jar的一种打包文件。
对于PHP5.3或更高版本,phar后缀文件默认开启
phar产生反序列化的原因
在使用phar://协议读取文件时,文件会被解析成phar
解析过程中会触发php_var_unserialize()函数对meta-data的操作,造成反序列化。
文件包含:phar伪协议,可读取.phar文件
Phar
是php
压缩文档。它可以把多个文件归档到同一个文件中,而且不经过解压就能被 php
访问并执行,与 file://
、 php://
等伪协议类似,也是一种流包装器。
php中一些常见的流包装器如下:
要想使用 Phar
类里的方法,必须将 php.ini
文件中的 phar.readonly
配置项配置为 0
或 Off
(默认为 On
)
phar结构
stub phar 文件标识,格式为xxx<?php xxx;__HALT_COMPILER(); ?>;
(头部信息)
manifest:压缩文件的属性等信息,以序列化存储
content:被压缩文件的内容
signature (可空):签名,放在末尾。
phar利用条件
- phar文件能上传到服务器端
- 要有可用反序列化魔术方法作为跳板
- 要有文件操作函数,如file_exists(),fopen,file_get_contents()
- 文件操作函数参数可控,且:、/、phar等特殊字符没有被过滤
生成phar文件
编辑.php文件如下
假设上述程序代码保存为1.php
那么只需要执行(前提是php已经设置于环境变量中,或者跑到php程序目录打开命令行)
即可生成phar.phar
可用010打开查看
当环境限制了phar不能开头,可以使用以下伪协议绕过
当环境限制了phar不能出现在前面的字符里,还可以配合其他协议进行利用。
php://filter/read=convert.base64-encode/resource=phar://phar.phar
GIF格式验证可以通过在文件头部添加GIF89a绕过
1、$phar->setStub(“GIF89a”.“”); //设置stub
2、生成一个phar.phar,修改后缀名为phar.gif
除了 file_put_contents
外,会把 phar
反序列化的函数还有:
受影响的函数列表 | |||
---|---|---|---|
filename |
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 |
原生类
不想做笔记了,去看大佬博客,一天看八百遍,反复温习
Error/Exception XSS
SplFileObject 读文件
DirectoryIterator 遍历目录
FilesystemIterator 遍历目录
SoapClient SSRF
- 需要有
soap
扩展,需要手动开启该扩展。 - 需要调用一个不存在的方法触发其
__call()
函数。 - 仅限于
http
/https
协议
利用原生类 SoapClient
实现 SSRF
,构造 SoapClient
的类对象,需要有两个参数字符串 $wsdl
和数组 $options
tricks
php7.1+反序列化对类属性不敏感
我们前面说了如果变量前是protected,序列化结果会在变量名前加上\x00*\x00
但在特定版本7.1以上则对于类属性不敏感,比如下面的例子即使没有\x00*\x00
也依然会输出abc
绕过__wakeup(CVE-2016-7124)
版本:
PHP5 < 5.6.25
PHP7 < 7.0.10
利用方式:序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行
绕过部分正则
匹配序列化字符串是否含有o,c,:冒号之后\d是匹配数字不区分大小写,我们可以利用+绕过
preg_match('/^O:\d+/')
^O,匹配序列化字符串是否是对象字符串开头
-
利用加号绕过(注意在url里传参时+要编码为%2B
-
还可以用数组绕过,serialize(array(a ) ) ; / / a为要反序列化的对象(序列化结果开头是a,不影响作为数组元素的$a的析构)
利用引用
上面这个例子将$b
设置为$a
的引用,可以使$a
永远与$b
相等
16进制绕过字符的过滤
__EOF__

本文链接:https://www.cnblogs.com/solitude0-c/p/17599688.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!