酷播亮新聞
最棒的知識補給站

、小白都能看懂的JSON反序列化遠端命令執行

*本文原創作者:TopScrew,本文屬FreeBuf原創獎勵計劃,未經許可禁止轉載

前言

Fastjson是一個由阿里巴巴維護的一個json庫。它採用一種“假定有序快速匹配”的演算法,是號稱Java中最快的josn庫。Fastjson介面簡單易用,已經被廣泛使用在快取序列化、協議互動、Web輸出、Android客戶端等多種應用場景。今天我們就以最詳細的姿勢,一步步分析一下FastJson的遠端命令執行!

0×01序列化

先熟悉一下FastJson的用法,畢竟連用法都不會怎麼分析漏洞。下面用在最簡單的示例快速入門一下FastJson

簡單建立了一個實體bean,並set了兩個屬性值。進行簡單的序列化,看看序列化後是不是變成了我們想要的東西。至於WriteClassName的作用,序列化時寫入型別資訊,預設為false。反序列化是需用到。

此時,已經非常完美的序列化成了我們常見的json資料。而加了WriteClassName屬性的序列化,多了一個@type,也就是我們當時建立的那個實體物件。

0×02反序列化

反序列化的用法也比較簡單,也就是將toJSONString換成parseObject即可。第一個引數是json字串,第二個引數就是前面說到的@type實體物件。

成功的將字元反序列化位了實體物件。

0×03靜態分析

分析漏洞最好的方式就是看看他到底做了什麼防禦,從他的補丁入手。

從更新的補丁來看,官方增加了一個checkAutoType方法,看到check這個詞大概就能想到這塊估計是是做了一個黑名單。跟進這個方法

發現checkAutoType方法對denyList列表進行了便利。跟進checkAutoType看一看。

public Class checkAutoType(StringtypeName, Class expectClass) {

if (typeName == null) {

return null;

}

final String className = typeName.replace(‘$’, ‘.’);

if (autoTypeSupport || expectClass != null) {

for (int i = 0; i < acceptList.length; ++i) {

String accept = acceptList[i];

if (className.startsWith(accept)){

returnTypeUtils.loadClass(typeName, defaultClassLoader);

}

}

for (int i = 0; i < denyList.length; ++i) {

String deny = denyList[i];

if (className.startsWith(deny)){

throw newJSONException(“autoType is not support. ” + typeName);

}

}

}

Class clazz = TypeUtils.getClassFromMapping(typeName);

if (clazz == null) {

clazz = deserializers.findClass(typeName);

}

if (clazz != null) {

if (expectClass != null && !expectClass.isAssignableFrom(clazz)){

throw newJSONException(“type not match. ” + typeName + ” -> ” +expectClass.getName());

}

return clazz;

}

if (!autoTypeSupport) {

for (int i = 0; i < denyList.length; ++i) {

String deny = denyList[i];

if (className.startsWith(deny)){

throw newJSONException(“autoType is not support. ” + typeName);

}

}

for (int i = 0; i < acceptList.length; ++i) {

String accept = acceptList[i];

if(className.startsWith(accept)) {

clazz =TypeUtils.loadClass(typeName, defaultClassLoader);

if (expectClass != null&& expectClass.isAssignableFrom(clazz)) {

throw newJSONException(“type not match. ” + typeName + ” -> ” +expectClass.getName());

}

return clazz;

}

}

}

if (autoTypeSupport || expectClass != null) {

clazz = TypeUtils.loadClass(typeName, defaultClassLoader);

}

if (clazz != null) {

if (ClassLoader.class.isAssignableFrom(clazz) // classloader is danger

||DataSource.class.isAssignableFrom(clazz) // dataSource can load jdbc driver

) {

throw newJSONException(“autoType is not support. ” + typeName);

}

if (expectClass != null) {

if(expectClass.isAssignableFrom(clazz)) {

return clazz;

} else {

throw newJSONException(“type not match. ” + typeName + ” -> ” +expectClass.getName());

}

}

}

if (!autoTypeSupport) {

throw new JSONException(“autoType is not support. ” +typeName);

}

return clazz;

}

}

首先他會先判斷expectClass是否為空如果為空的化,就會去檢查denyList這個列表。看名字就知道是一個黑名單列表。看看這個列表裡都有什麼東西。

當我們引入的庫是以列表中任何一個欄位開頭時就報throw newJSONException(“autoType is not support. ” + typeName);的異常。再看看網上的poc引入的庫com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl。看來當迴圈到com.sun時就丟擲了異常。阻止了對惡意物件反序列化的執行。當然這是知道了網上流傳的poc,利用前輩們的poc分析起來就輕鬆多了。

0×05構造poc

當然引入poc以前,再熟悉json和java的應用。

依舊是新建一個實體bean,但是現在要注意兩個地方,一個是我設定了兩個屬性。二是我忘無參構造器裡寫入了一條碳計算器的命令。接下來我們看看會發生什麼。

神奇的地方發生了,當json反序列化時會自動呼叫無參構造器裡的方法,導致計算機彈出。但是還有一點大家有沒有注意到,我上面的json字串明明有password=123456為什麼沒有反序列化出來。答案是因為我的PassWord欄位設定的是私有屬性,所以FastJson無權直接去反序列化私有欄位。只是我們構造poc的一點java基礎知識。

這已經能執行系統命令了,是不是把我們的實體bean直接傳給伺服器,伺服器就可以讓我們為所欲為了呢?當然不是的,因為這個只是我們自己構造的實體bean,只有在自己環境才能認識,除非將實體bean直接上傳到伺服器。那就有點扯淡了………

言歸正傳,現在的第一步就是學習java反序列化的思想,想盡辦法在jdk和fastjson中,伺服器肯定存在的程式碼中找我們想要的東西。這時就該引出前輩們的poc中的com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl類,那就跟進去看一看

getTransletInstance方法生成一個translet類的例項

_bytecodes位元組陣列又是translet類的實際類定義。這樣我們是不是就以其他的方式代替了將惡意類上傳到伺服器這個不可取的方法了呢。

private void defineTransletClasses()

throws TransformerConfigurationException {

if (_bytecodes == null) {

ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);

throw new TransformerConfigurationException(err.toString());

}

TransletClassLoader loader = (TransletClassLoader)

AccessController.doPrivileged(new PrivilegedAction() {

public Object run() {

return newTransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());

}

});

try {

final int classCount = _bytecodes.length;

_class = new Class[classCount];

if (classCount > 1) {

_auxClasses = new Hashtable();

}

for (int i = 0; i < classCount; i++) {

_class[i] =loader.defineClass(_bytecodes[i]);

final Class superClass =_class[i].getSuperclass();

// Check if this is the mainclass

if(superClass.getName().equals(ABSTRACT_TRANSLET)) {

_transletIndex = i;

}

else {

_auxClasses.put(_class[i].getName(), _class[i]);

}

}

if (_transletIndex < 0) {

ErrorMsg err= newErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);

throw newTransformerConfigurationException(err.toString());

}

}

catch (ClassFormatError e) {

ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);

throw new TransformerConfigurationException(err.toString());

}

catch (LinkageError e) {

ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);

throw new TransformerConfigurationException(err.toString());

}

}

首先_bytecodes會傳入getTransletInstance方法中的defineTransletClasses方法,defineTransletClasses方法會根據_bytecodes位元組陣列new一個_class,_bytecodes載入到_class中,最後根據_class,用newInstance生成一個java例項。

此時可以看到已經成功生成了我們的惡意程式碼com.screw.test.Demo.但是還要注意另一個標註點。強制型別轉化為AbstractTranslet類,這是就知道為什麼構造的惡意程式碼一定要繼承AbstractTranslet類了。

0×04障礙解決

現在還有一個問題,怎麼去觸發getTransletInstance接下來會將大家帶入一個好玩的呼叫鏈。感覺這個巧妙程度和當時的java反序列化有得一拼。

在newTransformer中觸發了getTransletInstance方法,那問題又來了怎麼觸發newTransformer?

getOutputProperties()方法出場了,在return處呼叫了newTransformer方法。繼續尋找getOutputProperties方法。

很快找到了這個_outputProperties屬性,只要呼叫他的get方法是不是就出發了getOutputProperties方法了呢?但是非常遺憾的是_outputPropertie屬性前面有一個下劃線,呼叫get方法是觸發的是get_ OutputProperties方法,而且這個屬性還是一個私有屬性,不知道大家還記不記得我前面的實驗,FastJson不能直接使用實體bean中的私有方法,沒有達到我們的目標怎麼辦?

JavaBeanDeserializer中的smartMatch方法會將傳入的key的_替換為空。

這張是動態除錯的結果,很明顯key2已經從_OutputProperties變成了OutputProperties。至此所有的阻礙已經去除。

0×05呼叫鏈

0×06最終POC

惡意類

poc

這塊放出了完整的poc和惡意類可以結合呼叫鏈再回顧一下整個的反序列化過程!

總結:

FastJson雖然被廣泛利用但是不知道大家有沒有看到,Feature.SupportNonPublicField這個屬性就是最後一個坑。他是在1.2.22版本才引入的,在1.2.25版本就被修復了。導致這個漏洞特別的稀少。雖然漏洞沒有什麼太大的利用價值,但是最重要的是我們學到了大佬的挖洞思路,我想這才是最有價值的東西。

Comments

comments

如有侵權請來信告知:酷播亮新聞 » 、小白都能看懂的JSON反序列化遠端命令執行