前言
将对象转为字节流存储到硬盘上,当JVM停机的话,字节流还会在硬盘上默默等待,等待下一次JVM的启动,把序列化的对象,通过反序列化为原来的对象,并且序列化的二进制序列能够减少存储空间(永久性保存对象)
序列化成字节流形式的对象可以进行网络传输(二进制形式),方便了网络传输。
php反序列化
序列化格式
序列化后的内容只有成员变量,没有成员函数
1 | O:4:"eeee":1: |
例如·
1 |
|
pop链
在反序列化后,会进行变量覆盖(就算原本的类里没有声明这个变量也可以覆盖).不会触发类方法,但会触发魔术方法
魔术方法
1.首先需要触发反序列化
魔术方法的对象为类,pop链一般由__destruct()和__wakeup()触发(即反序列化的结束),如果在__destruct和__wakeup()中
2.(以不恰当的方法处理对象),而
3.(对象中恰有相对应的魔术方法),就会触发该方法,不断触发魔术方法后,最终在最后一个魔术方法中触发危险函数以达到目的
1 | __construct 当一个对象创建时被调用, |
__construct()和__destruct()
__construct:当对象创建时会自动调用,也就是说有new的时候就会调用,在unserialize时是不会被自动调用的,此方法常用在构造反序列化的pop链上
__destruct():当对象被销毁时会自动调用;
__sleep()和__wakeup()
__sleep() :在对象被序列化之前被调用,就是说看到serialize时就会被调用,而且是先调用后再执行序列化
__wakeup(): 将在字符串被反序列化之后被立即调用,就是说看到unserialize后就会被立即调用(优先等级比__destruct()高)
wakeup()魔法函数绕过(PHP反序列化漏洞CVE-2016-7124)
适用php版本
PHP5.0.0<5.6.25
PHP7.0.0<7.0.10
当反序列化字符串中,表示属性个数的值大于真实属性个数时,会绕过 __wakeup 函数的执行
php反序列化字符逃逸
$str='O:6:"people":2:{s:5:"name";s:3:"aaa";s:3:"sex";s:3:"boy";}';
在php序列化后,形成的字符串的大小和元素的数量是被限定的,且受格式的限制,贸然处理该字符串使字符串的某个‘值’大小变化,会导致字符串无法被反序列化
输入恰好的字符串长度,使其处理后还符合php序列化格式,让无用的部分字符逃逸或吞掉,从而达到我们想要的目的。
序列化后的字符串在进行反序列化操作时,会以{}两个花括号进行分界线,花括号以外的内容不会影响反序列化。
字符减少
处理前
a:2:{s:7:”flagphp”;s:48:”;s:1:”a”;s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;}”;
s:3:”img”;s:20:”Z3Vlc3RfaW1nLnBuZw==”;}
处理后
a:2:{s:7:””;s:48:”;s:1:”a”;
s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;}”;
s:3:”img”;s:20:”Z3Vlc3RfaW1nLnBuZw==”;}
字符增多
处理前
O:1:”A”:2:{s:4:”name”;s:52:”aaaaaaaaaaaaaaaaaaaaaaaaaa”;s:6:”passwd”;s:3:”123”;}”;s:6:”passwd”;s:3:”321”;}
处理后
O:1:”A”:2:{s:4:”name”;s:52:”bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb”;s:6:”passwd”;s:3:”123”;}”;s:6:”passwd”;s:3:”321”;}
Session反序列化
在 PHP 中启动会话,session_start — 启动新会话或者重用现有会话 session_start(array $options = array()): bool
session.save_path:这个是session的存储路径,也就是sess_session_id文件存储的路径
session.auto_start:这个开关是指定是否在请求开始时就自动启动一个会话,默认为Off
session.save_handler:这个是设置用户自定义session存储的选项,默认是files,也就是以文件的形式来存储的
- 启动新会话
在默认files的情况下,如果没要session或者COOKIE中的PHPSESSID没有对应的文件,则会新生成一个session_id,存入COOKIE中的PHPSESSID中,并生成一个名为“sess_”+“session_id”的文件。当有写入$_SESSION的时候,就会往sess_文件里序列化写入数据。
若session.use_strict_mode值为0(默认值)。此时用户是可以自己定义Session ID的。比如,我们在Cookie里设置PHPSESSID=TGAO,PHP将会在服务器上创建一个文件:/tmp/sess_TGAO”。 - 重用现有会话
当会话自动开始或者通过 session_start() 手动开始的时候, PHP 内部会调用会话管理器的 open 和 read 回调函数。通过 read 回调函数返回的现有会话数据(使用特殊的序列化格式存储), PHP 会自动反序列化数据并且填充 $_SESSION 超级全局变量。session序列化存储的格式
session.serialize_handler,用来定义session序列化存储所用的处理器的名称,不同的处理器序列化以及读取出来会产生不同的结果;默认的处理器为php - 处理器php
它处理之后的格式是键名+竖线|+经过serialize()序列化处理后的值wllm|s:4:"yyds";LTLT|s:5:"ddwhm";
- 处理器php_binary
键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数序列化处理后的值;WLLMS:4:""yyds; LTLTS:5:"ddwhm"
- 处理器php_serialize(php>5.5.4)
直接进行序列化,把session中的键和值都会被进行序列化操作,且放在一个数组中a:2:{s:4:"wllm":s:4:"yyds";s:4:"LTLT";s:5:"dddwh"}
Session反序列化原理
在重用现有会话,处理器php会以遇到的第一个“|”为分界线,前为键,后为值,将值反序列化,简单粗暴;
在我们传入的序列化内容前加一个分隔符|,利用不同处理器序列化的差异,序列化我们想要的结果
Session反序列化的利用
phar反序列化(5.3<=php<=7.4默认开启支持)
PHAR (“Php ARchive”) 是PHP里类似于JAR的一种打包文件。
phar://伪协议会把任何文件当作phar文件处理,并不经解压直接访问phar文件下的文件(如果phar中只有一个文件,则默认读取该文件,不受phar文件名后内容的影响)
a:1:{s:4:“name”;s:199:”|O:10:”SoapClient”:3:{s:3:”uri”;s:25:”http://127.0.0.1/flag.php";s:8:"location";s:25:"http://127.0.0.1/flag.php";s:13:"_soap_version";i:1;}"}
phar文件是由四部分组成的:
1.stub:可以理解为是phar文件的标志,就像GIF89a是图片头一样,它的格式为:xxxxx,前面内容没有限制,什么都行,只是结尾必须是__HALT_COMPILER();?>,否则将不能被识别为phar文件
2.manifest describing the contents:这里存放着压缩文件的信息,每个被压缩文件的权限,属性等信息都放在这里,这里还会以序列化的形式存储着用户自定义的meta-data,当php的文件操作函数通过phar://伪协议解析phar文件时会先自动先将meta-data进行反序列化
3.the file contents:被压缩文件的内容,这个随便是啥都不影响
4.signature for verifying Phar integrity:签名,放在文件末尾
其实这四条只有前面两条比较重要,后两条都是来打酱油的,总结下来就是两点:一是文件标识,必须以__HALT_COMPILER();?>结尾,但前面的内容是没有限制的,也就是说我们可以构造一个图片文件或者pdf文件来绕过上传的限制,将这个phar文件上传上去;二是phar文件存储meta-data时会先将内容序列化后再存入进去,当文件操作函数通过phar://伪协议解析phar文件时就会先将数据反序列化,这样就可以构成反序列化攻击,而文件操作函数有很多,下面就讲它
1 |
|
在有反序列化漏洞的文件下,利用文件操作函数(函数利用范围更广)以phar://伪协议访问我们上传的phar下的文件,就会使我们设置在该文件上的Metadata在系统文件下反序列化
php原生类的利用
PHP原生类就是在标准PHP库中已经封装好的类,而这里面有一些类可以实现目录遍历,文件读取,发起请求等
但其中只有一小部分是我们可以利用的,一般比较常见的如下:
Error实现XSS(php7)
类的构造方法接受两个可选参数 —— 一个消息字符串和一个错误码(用于分配不同的类)
同时这个类中还有一个内置方法**__toString()**的魔术方法,它会将Error以字符串的形式输出到页面上
格式为Error: <script>alert(1)</script> in D:\phpstudy_pro\WWW\2.php:13 Stack trace: #0 {main}
1 |
|
Exception内置类
适用于php5、7版本
开启报错的情况下
原理是类似的
- 文件类
- 遍历目录
1
2
3
4
5
6
7
8
9
10
11
$dirname = new DirectoryIterator("glob:///*f*");
echo $dirname;//DirectoryIterator类的__construct方法会构造一个迭代器,如果使用echo输出该迭代器,将触发__toString()而返回迭代器的第一项
?a=DirectoryIterator&b=glob:\/\/f* //可以使用glob协议
//FilesystemIterator类可以代替,使用方法和DirectoryIterator类差不多
$dir = new GlobIterator("/*.txt"); //自带glob
echo $dir;
//这三种遍历目录的方法可以无视open_basedir对目录的限制
- 读取文件一行完整读取
1
2
3
4
highlight_file(__file__);
$context = new SplFileObject('/f1agaaa');
echo $context;//每次只能读取文件中的一行内容?a=SplFileObject&b=php://filter/convert.base64-encode/resource=flag.php
利用SoapClient实现SSRF
WebService是一种跨平台,跨语言的规范,用于不同平台,不同语言开发的应用之间的交互。
SOAP,作为webService三要素(SOAP、WSDL、UDDI)之一
UDDI (Universal Description Discovery and Integration)通用描述发现和集成
WSDL (WebService Description Language) WebService描述语言
SOAP (Simple Object Access Protocol) 简单对象访问协议
可基于HTTP协议,采用XML格式,用来传递信息的格式。
PHP中的SoapClient类是用来创建soap数据报文,与wsdl接口进行交互的
1 | public __construct(?string $wsdl, array $options = []) |
类的构造方法接受两个可选参数—
第一个参数$wsdl用来指明是否为wsdl模式,在wsdl模式的情况下,$options参数是可选的,也就是说可以没有;
但在非wsdl模式下,就必须要设置location和uri选项,其中location是我们要将请求发送到的SOAP服务器的URL,也就是目标URL
在SoapClient类中,还有一个魔术方法,**__call()**方法,当触发这个方法后,它就会向location中的目标URL发送一个soap请求
参数$options里选项user_agent,用这个可以控制HTTP数据包中头部User-Agent的值
利用SoapClient进行SSRF攻击内网,然后配合CRLF构造出POST请求可以拓展我们的攻击面
1 |
|
2025/4/6补充-关于CRLF
因为这个漏洞过于简单,应用场景也不是很复杂,就是http请求或者响应生成时没过滤回车符(CR,ASCII 13,\r,%0d) 和换行符(LF,ASCII 10,\n,%0a),很多web语言现在都没有这个漏洞了,网上也没什么文章,所以就不值得我在写一篇文章记录了,想到之前打ctf有遇到过crlf就写在这里了。
CRLF分为两种一种是构造http响应包的CRLF,可以导致的反射xss,一开始我还很兴奋以为发现新的xss技巧那,但是这个目前只有那些转载面经里有提到,我用php5.2都没复现成功,太鸡肋了,实战中几乎不可能遇到。
一种就是构造http请求的CRLF,包括上文说到的php SoapClient,还有:
net/http < 1.11 CRLF https://github.com/golang/go/issues/30794
Python urllib CRLF 注入漏洞(CVE-2019-9740)[Python 2<2.7.16,Python 3<3.7.2]
这两个都是2019年的CRLF,请求包crlf漏洞的作用就是自定义数据包,然后打打redis什么的,先这样吧,也是解决了当时一直搞不明白的一个问题点,ctf里偏题太多了