Fastjson-autoType漏洞总结
文章目录
Fastjson is a Java library that can be used to convert Java Objects into their JSON representation. It can also be used to convert a JSON string to an equivalent Java object. Fastjson can work with arbitrary Java objects including pre-existing objects that you do not have source-code of.
可以由JSON.toJSONString(object)
将对象序列化为json字符串,由JSON.parseObject/parseArray
或JSON.parse
从json字符串中反序列化还原为对象。在序列化时指定开启SerializerFeature.WriteClassName
功能,会在生成的json字段中加入@type
用于指定具体类名,这样可以避免继承类被误反序列化为父类,从而丢失子类特有属性。
1.2.24一般流程简述
根据方法与传参的不同,入口可能对应多个重载方法,但总体上与漏洞相关的逻辑大同小异:
解析器初始化,调用解析方法
加载
@type
指定的类对象和与之对应的Deserializer
如果类名在
denyList
中就抛出异常(黑名单)如果是
JavaBeanDeserializer
,会通过内省获取方法和属性获取到
defaultConstructor
则会进入第六步将满足条件的setter与getter及相关属性,封装为
FieldInfo
类并加入fieldList
将
Deserializer
缓存进IdentityHashMap
获取各个属性对应的
FieldValueDeserilizer
,反序列化属性对象由
setValue
根据属性类型,按照不同方式还原类对象属性
调用getter
第六步getter对于返回值类型要求比较严格,可以利用Fastjson一些特性扩大范围。
对于JSON.parseObject(String text)
会先调用parse
解析,如果返回类型不是JSONObject
就通过toJSON
转换:
满足判断时可由JSONSerializer.getObjectWriter
->TypeUtils.buildBeanInfo
->computeGetters
获取getter且无返回值类型限制。因为JSONObject.toString
被重写成了toJSONString
,只需构造形如{{Gadgets}:"str"}
的结构即可触发。
此外对于JSON中$ref
的值,会在parse
完成后作为JSONPath解析,传入$.valuea.valueb
可以链式触发getValuea
和getValueb
,这样就能调用到指定getter。
指定还原类
当parseObject
方法指定的还原类与JSON中@type
指定的还原类不一致时,根据@香依香偎师傅的实验结果来看,如果两个类存在继承关系或是能由toString
等方法转换,则通常可以正常反序列化并触发Payload;否则要具体情况具体分析。
TemplatesImpl利用链
1 | { |
outputProperties
属性在setValue
时,会调用TemplatesImpl#getOutputProperties
,进而调用到TransletClassLoader#defineClass
触发类加载。
Payload几个关键属性都是private
且无相应setter/getter,因此反序列化时需要指定1.2.22版本引入的Feature.SupportNonPublicField
:
JdbcRowSetImpl利用链
1 | { |
autoCommit
属性在setValue
时,会调用JdbcRowSetImpl#setAutoCommit
,进而调用到InitialContext#lookup
触发JNDI注入。
checkAutoType绕过
从1.2.25开始先进入checkAutoType
检查,再调用TypeUtils.loadClass
加载类。
绕过黑名单
在手动开启autoTypeSupport
时,会将类名与内置黑名单进行前缀匹配。利用TypeUtils.loadClass
剔除L
前缀和;
后缀的特点,在原类名前后添加字符即可绕过内置黑名单的判断。
L ClassName ;
、[
分别是JVM的类、数组表示符
1.2.42先剔除一次类名前后的L
和;
再进行黑名单判断,同时将黑名单从明文改为哈希形式,可以双写字符绕过。
1.2.43检查两次前缀,拦截LL
开头的情况,可以构造[ClassName[{...
的数组形式,利用内部逻辑调用到parseArray
实现绕过。
1.2.44增加了对[
开头的检查,终结了已有黑名单绕过。如果存在某些依赖可以利用新链突破,后续更新也在继续完善黑名单。
利用缓存机制
1 | { |
以1.2.47为例,java.lang.Class
对应的deserializer是MiscCodec
,反序列化会解析JSON中val
对应的值并生成对象,之后通过TypeUtils.loadClass
加载java.lang.Class
。loadClass方法的cache
参数的缺省值为true,会将val
值生成的对象存入mappings
中。
下一轮checkAutoType
时如果手动开启了autoTypeSupport
,恶意类先黑名单匹配还是会被检测到,但只要在mappings
缓存中匹配到了就不会抛出异常。之后无论checkAutoType
是否开启,都能拿到缓存中的恶意类。
要注意的是虽然1.2.25就加入了缓存机制,但1.2.33之前匹配黑名单就会抛出异常。1.2.48将cache
缺省值改为false,新增了Class.forName
前的判断,同时追加了黑名单以及对expectClass
的检测。
利用期望类
期望类可以由类间关系隐式确定,也可以由两个@type
显式指定。一个期望类为Foo
的例子:
1 | class User { |
- 由类间关系确定
1 | { |
- 由JSON显式指定
1 | {"@type":"Foo","@type":"FooImpl","fooId":"abc"} |
从玄武实验室梳理的checkAutoType
的流程可以看出,期望类及其派生的子类不在黑名单中就能通过检查:
expectClass
由checkAutoType
的二参传入,多数情况下是null,先要找出存在有效参数的调用,一个是ThrowableDeserializer
、一个是JavaBeanDeserializer
在ParserConfig#getDeserializer
可以看到Throwable.class
的派生类由ThrowableDeserializer
处理、没有专属Deserializer的类由JavaBeanDeserializer
处理
哪些类会由JavaBeanDeserializer
处理呢?以TypeUtils#addBaseClassMappings
的classes
作为样本,提取getDeserializer
方法稍作改动并筛除expectClassFlag
为false的类后得到:
1 | java.lang.AutoCloseable |
- 1.2.68更新的
safeMode
机制用于完全禁用autoType
,手动开启后遇到@type
直接抛异常
寻找Gadgets
派生类中有可被自动调用的敏感方法,就是一个可选的潜在期望类,最好是一个被广泛使用的接口(AutoCloseable
表示你直接报我哈希得了(✪ω✪))。而魔术方法除了setter、getter、无参构造函数外,还可以调用 能通过ASMUtils.lookupParameterNames
获取到参数名 且重载参数最多的构造函数:
- 参数名存放于
LocalVariableTable
,只在编译时带调试信息才会有
OutputStream利用链
1 | { |
通过
FileOutputStream(File file, boolean append)
创建文件并作为InflaterOutputStream(OutputStream out, Inflater infl, int bufLen)
的一参二参
Inflater
类中存在ByteBuffer input
属性,经过JSONScanner#bytesValue
Base64解码得到byte后,会在ByteBufferCodec$ByteBufferBean#byteBuffer
完成组装(其中array
是zip压缩后byte的Base64编码,limit
是字节长度)最终由
MarshalOutputStream
的父类构造函数ObjectOutputStream
->this.bout.setBlockDataMode
->this.drain
->this.out.write
触发写入
要注意的是1.2.57才开始支持ByteBuffer反序列化,有调试信息的部分JDK8版本与这个OpenJDK11input
属性有不同。
MysqlJDBC利用链
1 | { |
JDBC4Connection类构造函数初始化时会使用父类ConnectionImpl的构造函数,进一步通过ServerStatusDiffInterceptor#populateMapWithSessionStatusValues
->Util.resultSetToMap
->rs.getObject
触发MysqlJDBC反序列化。
双亲委派类加载
通过System.getProperty
可以获取sun.boot.class.path
、java.ext.dirs
的值,根据双亲委派的特点如果能将恶意类上传/写入jre/classes
(这个目录似乎默认不存在,需要新建),或者覆盖已有的jar包(比如charsets.jar
),就可以由Fastjson触发恶意类加载。
1 | // 实现AutoCloseable |
参考链接
fastjson一种利用$ref几乎任意getter触发的方法
fastjson 1.2.68 反序列化漏洞 gadgets 挖掘笔记
Fastjson 1.2.68 反序列化漏洞 Commons IO 2.x 写文件利用链挖掘分析
eu-19-Zhang-New-Exploit-Technique-In-Java-Deserialization-Attack