URLDNS学习

URLDNS

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

虽然这个“利⽤链”实际上是不能“利⽤”的,但因为其如下的优点,⾮常适合我们在检测反序列化漏洞时使⽤:

  • 使⽤Java内置的类构造,对第三⽅库没有依赖 在⽬标没有回显的时候,能够通过DNS请求得知是否存在反序列化漏洞
  • ysoserial 会调⽤ URLDNS 类的 getObject ⽅法⽅法获得Payload。这个⽅法返回的是⼀个对象,这个对象就是最后将被序列化的对象,在这⾥是 HashMap

触发反序列化的⽅法是 readObject ,因为Java开发者(包括Java内置库的开发者)经常会在这⾥⾯写⾃⼰的逻辑,所以导致可以构造 gadget

由于payload被序列化的对象是HashMap,那就直接看HashMapreadObject方法。

其中最后一行有这样的语句:

1
putVal(hash(key), key, value, false, false);

跟进这个方法:

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

可见,hash⽅法调⽤了keyhashCode()⽅法。

URLDNS中使⽤的这个key是⼀个java.net.URL对象,我们看看其hashCode⽅法。

1
2
3
4
5
6
7
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;

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

此时,handlerURLStreamHandler对象(的某个⼦类对象),继续跟进其hashCode⽅法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Provides the default hash calculation. May be overidden by handlers for
* other protocols that have different requirements for hashCode
* calculation.
* @param u a URL object
* @return an {@code int} suitable for hash table indexing
* @since 1.3
*/
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);
...
}

这里调用了InetAddress addr = getHostAddress(u);
继续跟进:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}

这⾥ InetAddress.getByName(host) 的作⽤是根据主机名,获取其IP地址,在⽹络上其实就是⼀次 DNS查询。到这⾥就不必要再跟了。

这次URLDNS的整个gadget chain就清晰了。

要构造这个Gadget,只需要初始化⼀个 java.net.URL 对象,作为key放在 java.util.HashMap 中;然后,设置这个 URL 对象的 hashCode 为初始值 -1 ,这样反序列化时将会重新计算其 hashCode ,才能触发到后⾯的DNS请求,否则不会调⽤ URL->hashCode()
另外,ysoserial为了防⽌在⽣成Payload的时候也执⾏了URL请求和DNS查询,所以重写了⼀ 个 SilentURLStreamHandler 类,这不是必须的。

1
2
3
4
5
6
HashMap->readObject()
HashMap->hash()
URL->hashCode()
URLStreamHandler->hashCode()
URLStreamHandler->getHostAddress()
InetAddress->getByName()

Gadget:

1
2
3
4
5
Gadget chain:
HashMap.readObject()
HashMap.putVal()
HashMap.hash()
URL.hashCode()

最后摘抄p神在这篇文章写的一段话

学习过PHP反序列化的同学这时就会惊叹,这不就跟在PHP⾥找反序列化利⽤链⼀样吗?其实就是⼀样,那为什么很多同学觉得Java反序列化很难?多半其实是被 CommonsCollections 带偏了,这个链中确实有⼀些较难理解的概念。

有时候想要学习⼀个东⻄,⽹上搜索⼀下,发现有教程,于是跟着做⼀遍。这样⼀来,你会发现⽹上⼤ 部分Java反序列化“教程”、“⼊门”通常上来先了解Java反序列化是什么,然后很快开始讲 CommonsCollections ,就好像刚知道C语⾔语法的同学⽴⻢马继续学习Linux内核,我是⼗分不建议这样做的,除⾮你有⾮常强的理解能⼒。

学习需要聪明⼀点,并独⽴思考问题。我很少参照别⼈的⽂章来学习,这样你学的东⻄是⼆⼿的,有时候连⼆⼿都不是,⽂章原作者也可能是参考另⼀篇⽂章写的。 我的建议是从⽂档和源码开始学,实在有压⼒可以参考⼀些⻛风评较好的书籍或技术博客。

当然,有时候你并不知道怎样是对的怎样是错的。⽐如你作为⼀个Java反序列化漏洞的初学者,你并不知道应该先学习 URLDNS 还是 CommonsCollections ,也许你连 URLDNS 的名字都没听过,⽽你看到⽹上⼤部分⽂章都是介绍CommonsCollections的,⾃然也就去学习这个了。

所以有些弯路确实是避免不了的,不过如果你在学习的道路上更加富有探索精神,看到ysoserial这种项⽬时多问问⾃⼰“其他的Gadget是做什么的”,对于未知的事物原理充满好奇喜欢翻翻源码.

Reference:
反序列化篇(2)