JAVA-反序列化
# 反序列化
序列化:将内存中的对象压缩成字节流
反序列化:将字节流转化成内存中的对象
Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据、对象的类型和对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据,对象的类型,对象中存储的数据等信息,都可以用来在内存中创建对象。
序列化与反序列化的设计就是用来传输数据的。
当两个进程进行通信的时候,可以通过序列化反序列化来进行传输。能够实现数据的持久化,通过序列化可以把数据永久的保存在硬盘上,也可以理解为通过序列化将数据保存在文件中。
应用场景:
- 想把内存中的对象保存到一个文件中或者是数据库当中。
- 用套接字在网络上传输对象。
- 通过 RMI 传输对象的时候。
几种创建的序列化和反序列化协议
・JAVA 内置的 writeObject ()/readObject ()
・JAVA 内置的 XMLDecoder ()/XMLEncoder
• XStream
• SnakeYaml
• FastJson
• Jackson
# JAVA 内置的 writeObject ()/readObject ()
序列化:
反序列化
# 重写 readObject 造成漏洞
在原始对象中重写 private readObject
方法,在执行反序列化时候,会被默认优先调用
1 | //序列化目标Userdemo |
序列化后执行反序列化就会执行命令调出计算器
# toString 造成漏洞
如果对 obj 对象进行输出 默认调用原始对象的 toString 方法 而造成执行外部命令
反序列化:
# 可控其他类重写方法
ysoserial/src/main/java/ysoserial/payloads/URLDNS.java at master · frohoff/ysoserial (github.com)
- 正常代码中 创建对象 HashMap
- 用到原生态 readObject 方法去反序列化数据
- readObject 本来在 ObjectInputSteam
- HashMap 也有 readObject 方法
反序列化时调用 HashMap 里面的 readObject
Gadget Chain:
HashMap.readObject()
HashMap.putVal()
HashMap.hash()
URL.hashCode()
hashCode 执行结果 触发访问 DNS 请求 如果这里是执行命令的话 就是 RCE 漏洞
- 潜在的反序列化漏洞: 通过 SerializableTest 方法将 HashMap 对象序列化并输出到文件 “dns.txt”,然后通过 UnserializableTest 方法尝试反序列化。这里存在潜在的反序列化漏洞,因为如果恶意用户能够控制序列化的输入,可能导致远程命令执行(RCE)漏洞。
- Gadget Chain 分析: 通过 HashMap 的 readObject 方法,调用了 HashMap.putVal () 方法,最终调用了 URL.hashCode ()。如果 hashCode () 方法的实现涉及到远程资源的访问(例如 DNS 查询),那么在反序列化时就可能触发远程请求。
- 潜在的安全问题: 如果 URL.hashCode () 实现中有不受信任的代码,例如执行系统命令或访问远程资源,那么通过构造特定的 URL 对象,攻击者就能够触发恶意操作。
# p 牛的 java 安全漫谈
先从 URLDNS 开始看起,因为它⾜够简单。
# ysoserial
什么是利用连?
利⽤链也叫 “gadget chains”,我们通常称为 gadget。如果你学过 PHP 反序列化漏洞,那么就可以将 gadget 理解为⼀种⽅法,它连接的是从触发位置开始到执⾏命令的位置结束,在 PHP ⾥可能是 __desctruct 到 eval
ysoserial 的使⽤也很简单,虽然我们暂时先不理解 CommonsCollections ,但是⽤ ysoserial 可以很容
易地⽣成这个 gadget 对应的 POC:
java -jar ysoserial-master-30099844c6-1.jar CommonsCollections1 "id"
如上,ysoserial ⼤部分的 gadget 的参数就是⼀条命令,⽐如这⾥是 id 。⽣成好的 POC 发送给⽬标,如果⽬标存在反序列化漏洞,并满⾜这个 gadget 对应的条件,则命令 id 将被执⾏。
# URLDNS
上面再开发的角度有过利用
URLDNS 就是 ysoserial 中⼀个利⽤链的名字,但准确来说,这个其实不能称作 “利⽤链”。因为其参数不是⼀个可以 “利⽤” 的命令,⽽仅为⼀个 URL,其能触发的结果也不是命令执⾏,⽽是⼀次 DNS 请求。
虽然这个 “利⽤链” 实际上是不能 “利⽤” 的,但因为其如下的优点,⾮常适合我们在检测反序列化漏洞时使⽤:
- 使⽤ Java 内置的类构造,对第三⽅库没有依赖
- 在⽬标没有回显的时候,能够通过 DNS 请求得知是否存在反序列化漏洞
接着 p 牛的思路,分析一下 ysoserial 是如何⽣成 URLDNS 的代码的:
1 | package ysoserial.payloads; |
整个 URLDNS 的 Gadget 其实清晰⼜简单:
- HashMap->readObject()
- HashMap->hash()
- URL->hashCode()
- URLStreamHandler->hashCode()
- URLStreamHandler->getHostAddress()
- InetAddress->getByName()
从反序列化最开始的 readObject ,到最后触发 DNS 请求的 getByName ,只经过了 6 个函数调⽤,这在 Java 中其实已经算很少了。
要构造这个 Gadget,只需要初始化⼀个 java.net.URL 对象,作为 key 放在 java.util.HashMap 中;然后,设置这个 URL 对象的 hashCode 为初始值 -1 ,这样反序列化时将会重新计算其 hashCode ,才能触发到后⾯的 DNS 请求,否则不会调⽤ URL->hashCode () 。
另外,ysoserial 为了防⽌在⽣成 Payload 的时候也执⾏了 URL 请求和 DNS 查询,所以重写了⼀个 SilentURLStreamHandler 类,这不是必须的。
# CommonCollections1 利⽤链
1 | package org.vulhub.Ser; |
TransformedMap
TransformedMap ⽤于对 Java 标准数据结构 Map 做⼀个修饰,被修饰过的 Map 在添加新的元素时,将可以执⾏⼀个回调。我们通过下⾯这⾏代码对 innerMap 进⾏修饰,传出的 outerMap 即是修饰后的 Map:
1 | Map outerMap = TransformedMap.decorate(innerMap, keyTransformer,valueTransformer); |
其中,keyTransformer 是处理新元素的 Key 的回调,valueTransformer 是处理新元素的 value 的回调。我们这⾥所说的” 回调 “,并不是传统意义上的⼀个回调函数,⽽是⼀个实现了 Transformer 接⼝的类。
Transformer
Transformer 是⼀个接⼝,它只有⼀个待实现的⽅法:
1 | public interface Transformer { |
TransformedMap 在转换 Map 的新元素时,就会调⽤ transform ⽅法,这个过程就类似在调⽤⼀个” 回调函数 “,这个回调的参数是原始对象。
ConstantTransformer
ConstantTransformer 是实现了 Transformer 接⼝的⼀个类,它的过程就是在构造函数的时候传⼊⼀个对象,并在 transform ⽅法将这个对象再返回:
1 | public ConstantTransformer(Object constantToReturn) { |
所以他的作⽤其实就是包装任意⼀个对象,在执⾏回调时返回这个对象,进⽽⽅便后续操作。
InvokerTransformer
InvokerTransformer 是实现了 Transformer 接⼝的⼀个类,这个类可以⽤来执⾏任意⽅法,这也是反序列化能执⾏任意代码的关键。
在实例化这个 InvokerTransformer 时,需要传⼊三个参数,第⼀个参数是待执⾏的⽅法名,第⼆个参数是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表:
1 | public InvokerTransformer(String methodName, Class[] paramTypes, Object[]args) { |
后⾯的回调 transform ⽅法,就是执⾏了 input 对象的 iMethodName ⽅法:
1 | public Object transform(Object input) { |
ChainedTransformer
ChainedTransformer 也是实现了 Transformer 接⼝的⼀个类,它的作⽤是将内部的多个 Transformer 串在⼀起。通俗来说就是,前⼀个回调返回的结果,作为后⼀个回调的参数传⼊,我们画⼀个图做示意:
1 | public ChainedTransformer(Transformer[] transformers) { |
最终
创建了⼀个 ChainedTransformer,其中包含两个 Transformer:第⼀个是 ConstantTransformer,直接返回当前环境的 Runtime 对象;第⼆个是 InvokerTransformer,执⾏ Runtime 对象的 exec ⽅法,参数是 /System/Applications/Calculator.app/Contents/MacOS/Calculator 。
当然,这个 transformerChain 只是⼀系列回调,我们需要⽤其来包装 innerMap,使⽤的前⾯说到的 TransformedMap.decorate :
1 | Map innerMap = new HashMap(); |
最后,怎么触发回调呢?就是向 Map 中放⼊⼀个新的元素:
1 | outerMap.put("test", "xxxx"); |
- Title: JAVA-反序列化
- Author: Fc04dB
- Created at : 2024-08-29 22:35:41
- Updated at : 2024-09-14 13:53:35
- Link: https://redefine.ohevan.com/2024/08/29/JAVA-反序列化/
- License: This work is licensed under CC BY-NC-SA 4.0.