Java安全20年:从BlackHat20年议题看Java安全发展趋势

摘要以2002—2026年Black Hat代表性议题为线索,回顾Java安全研究从JVM沙箱、客户端利用,到反序列化、JNDI、中间件、解析差异和运行时治理的变化。

Java 安全Black HatJVM反序列化JNDI

如果把 Java 的安全史整理成一份 CVE 清单,它很快就会膨胀成一本翻不到头的流水账。漏洞数量庞大,编号一年比一年长,但真正读下来,却很难看清两件更要紧的事:研究的重心是怎样一步步转移的,以及攻击者为什么总能在 Java 生态里找到新的入口。

Black Hat 历年的议题,正好适合用来梳理这条脉络。它算不上完整的漏洞数据库,也无法代表某一年的全部研究成果,却能清晰地反映出几个层面的变化:研究者当时最关注什么,哪些攻击手法已经成熟落地,又有哪些问题正从小范围的学术探讨走向大规模的真实攻击。

本文覆盖 2002—2026 年。标题里的“20年”是二十余年的概括说法,实际观察区间接近 24 年。

以 Black Hat 历年议题为线索,展示研究者注意力从 JVM 沙箱与客户端,逐步转向服务端反序列化、JNDI、攻击链自动化,直到 2026 年的表示差异、中间件认证前缺陷与 JVM 内存马。
图 1:Java 安全研究重心的迁移(2002—2026)

一、2002—2010:最先被盯上的,是 JVM 这道关口

2002 年的 Black Hat Asia 有一个很直接的题目:《Java and Java Virtual Machine Security Vulnerabilities and Their Exploitation Techniques》。议题内容包括 Java 内置安全机制、Applet sandbox、SecurityManager、bytecode verifier,以及当时不同 JVM 实现中的安全漏洞和 sandbox escape 技术。[1]

同年的 Black Hat USA 还有《Security Aspects in Java Bytecode Engineering: A Tutorial》,内容从 JVM 架构和 bytecode 讲起,也讨论了如何借助字节码工具分析和修改程序。[2]

这与当时 Java 的典型用法高度吻合。浏览器会从网络上下载 Applet,再交给本地 JVM 执行。远程代码固然能跑起来,但理论上只能被关在沙箱里,无法随意读取文件、发起网络连接,或调用本地资源。

当时最重要的几道防线是:

  • bytecode verifier 检查 class 文件和指令是否合法;
  • ClassLoader 区分本地可信代码和远程代码;
  • SecurityManager 根据策略限制敏感操作;
  • Applet sandbox 把远程代码关在一个受限环境中。

因此早期的 Java 安全研究,大多围绕类型安全、类加载和权限检查展开。只要能让 JVM 对某段 bytecode 的类型判断出现偏差,或是诱使高权限类替不可信代码执行操作,沙箱就有可能被绕过。

JavaSnoop 把问题拉回到客户端信任

到了 2010 年,Black Hat USA 的《JavaSnoop: How to Hack Anything Written in Java》给出了另一种思路:直接挂载到正在运行的 JVM 上,修改 class bytecode、方法参数与返回值,甚至能在对象进入网络之前将其拦截、篡改。[3]

同一届会议的《Hacking Java Clients》同样把重点放在 Java 客户端本身,探讨了客户端的业务逻辑、授权判断,以及运行时对象一旦被篡改会带来哪些后果。[3:1]

这批研究把一个朴素、却常被忽视的事实摆到了台面上:

只要代码运行在用户控制的机器上,客户端里的权限判断就不能算真正的安全边界。

许可证校验、角色判断、金额计算、功能开关,都可以放在客户端以改善体验,但服务端必须再做一次校验。混淆、签名和私有字段顶多抬高分析门槛,并不能改变"客户端始终掌握在用户手里"这个前提。

二、2011—2013:Java 浏览器插件进入利用高峰

很多人习惯把 Java 称作"内存安全语言",但这一说法其实只在语言层面部分成立。JVM、JIT、JNI 以及字体和图形处理等组件里,仍然存在大量 native code。

Black Hat Abu Dhabi 2011 的《Exploiting Memory Corruption Vulnerabilities in the Java Runtime》专门讨论了 JRE 中 memory corruption 漏洞的利用,以及开发可靠 exploit 时需要处理的问题。[4]

2012 年的《Recent Java Exploitation Trends and Malware》则把视角放到真实攻击。官方摘要提到,当时越来越多 Java 漏洞被用于恶意软件传播;Java 的跨平台特性让同一个 sandbox breach 有机会覆盖 Windows、macOS 和 Unix 系统。议题还以 CVE-2012-0507 为例,说明这类漏洞已经被用于大规模感染。[5]

这一阶段 Java 对攻击者很有吸引力,原因不复杂:

  1. 桌面安装量大;
  2. 浏览器插件直接接收网络内容;
  3. 很多终端长期没有升级 JRE;
  4. sandbox escape 往往比完整的 native exploit chain 更容易稳定;
  5. 一套 Java exploit 可以覆盖多个操作系统。

2013 年的《Java Every-Days: Exploiting Software Running on 3 Billion Devices》又往前走了一步。议题明确提到,研究重点已经不再局限于传统 memory corruption,而是开始利用 reflection API 等 Java 动态能力实现 remote code execution。[6]

换句话说,攻击者不一定非要破坏堆内存。只要能绕过访问限制,拿到原本不该访问的类、方法或对象,同样可以调用高权限 API。

这段历史,到 2026 年基本画上了句号

早期 Java 沙箱依赖 SecurityManager 和 Applet。后来浏览器插件退出主流,平台自身也开始清理这些旧机制。

JEP 411 在 JDK 17 中把 SecurityManager 标记为待移除;JEP 486 在 JDK 24 中将其永久禁用;JEP 504 则在 JDK 26 中移除了 Applet API。[7][8][9]

这并不意味着 Java 不再需要隔离,而是隔离的位置发生了转移。如今更可靠的边界,往往落在 JVM 之外——独立进程、容器、操作系统用户、文件权限、网络策略以及云身份。

三、2014—2019:主战场转向服务器端

浏览器插件退场之后,Java 安全并没有随之降温。企业内部的 Java Web 应用、RPC、消息系统、应用服务器和中间件,成了新的主要攻击面。

运行时数据流开始被自动追踪

Black Hat USA 2014 的 RAVAGE,全名是《Runtime Analysis of Vulnerabilities and Generation of Exploits》。它通过正常程序执行追踪完整 dataflow,用于发现漏洞、生成 exploit,并把结果接入现有测试框架。研究者还公开了 Java 版本。[10]

这类工作很适合 Java。class 文件格式统一,类型和方法信息相对完整,JVM 又提供 instrumentation 能力。研究工具可以在不拿到完整源码的情况下观察方法调用、对象传播和敏感操作。

从这里开始,Java 漏洞分析不再只是搜一个危险函数,而是要回答三个问题:

  1. 外部输入从哪里进来;
  2. 中间经过了哪些对象、回调和类型转换;
  3. 最后是否真的能到达文件、网络、进程执行或动态类加载。

2016 年,Java Serialization 成为企业级攻击入口

Black Hat USA 2016 的《Pwning Your Java Messaging with Deserialization Vulnerabilities》把 Java deserialization 放进了消息系统。议题指出,Java 生态中的消息处理大量依赖 Java Serialization,因此 message consumer 也可能暴露不安全反序列化入口。[11]

Java 原生反序列化的问题,远不止"解析了一个不可信文件"这么简单。ObjectInputStream.readObject() 恢复的是一整张对象图,恢复过程中还会顺带触发 readObjectreadResolvecompare、getter 等方法。攻击者根本不必往服务器上传新代码,只要目标 classpath 里凑齐了合适的类,就能把这些看似正常的方法串成一条 gadget chain。

一条典型的链路通常由四部分组成:

  1. 网络、文件、消息或 RPC 中,出现攻击者可控的序列化数据;
  2. 服务端调用 Java Object Serialization 还原对象;
  3. 对象图依次触发一组已有类的方法;
  4. 最终落入文件写入、网络访问、脚本执行、模板执行或进程创建等 sink。

入口往往只有一个,但真正决定能否利用的,却是整个 classpath。应用的依赖越多,候选 gadget 就越多,判断的难度也随之上升。

从攻击者可控的序列化数据,经服务端调用 readObject 还原对象图,依次触发已有类的方法拼成 gadget chain,最终抵达命令执行等危险 sink;入口只有一个,可利用性取决于整个 classpath。
图 2:Java 原生反序列化的四段式利用链

同一年,JNDI 被正式确立为一种攻击原语

Black Hat USA 2016 的另一个关键议题是《A Journey from JNDI/LDAP Manipulation to Remote Code Execution Dream Land》。研究者提出了 "JNDI Reference Injection",并指出:一旦攻击者可控的名称被传入 JNDI lookup,就可能借助 RMI、LDAP、CORBA 等服务,反过来影响执行查询的服务器。[11:1]

JNDI 原本是企业 Java 的基础设施 API,用于查找 DataSource、EJB、消息队列和目录对象。问题恰恰出在:应用把查询名称、Provider URL 乃至目录内容,交给了不可信输入去决定。

真正的危险点,通常不是 InitialContext.lookup() 这一个方法,而是下面几个条件同时成立:

  • 外部输入能够控制 lookup 的名称或目标;
  • JNDI provider 支持远程对象或引用;
  • 服务端能够主动访问攻击者可控的网络位置;
  • 查询结果会触发对象创建、类加载或其他动态行为。
攻击者控制 lookup 名称,诱导服务端主动访问其恶意 LDAP 或 RMI 服务,服务端拉取并加载攻击者返回的远程引用,最终在服务端触发代码执行;egress 管控可掐断服务端出网这一步。
图 3:JNDI 注入 —— 诱导服务端主动出网并加载远程类

这条研究线索,后来会在 Log4Shell 中再度浮现。

换成 JSON,不代表对象注入问题消失

Java deserialization 大规模曝光后,不少团队的第一反应是改用 JSON。Black Hat USA 2017 的《Friday the 13th: JSON Attacks》正是针对这种想当然的结论。研究者分析了 Java 和 .NET 的主流 JSON parser,证明部分库在默认或常见配置下依然能被打成 RCE,并把这套思路推广到了其他 serialization format。[12]

问题的关键,从来不在于数据长得像二进制、XML 还是 JSON,而在于 parser 会不会根据输入去决定具体的类型。

一旦外部数据能够控制 polymorphic type、类名、构造过程或属性 setter,所谓的"数据解析"就可能悄然变成对象实例化。只要后面还能接上危险类和 gadget,换个数据格式并没有真正切断攻击路径。

2018 年:gadget chain 走向自动化,组件间的解释差异也进入视野

Black Hat USA 2018 的《Automated Discovery of Deserialization Gadget Chains》提出了自动发现 Java gadget chain 的方法。在此之前,很多工具能定位不安全的 deserialization entry point,但要判断它究竟能不能利用,往往还得靠人工翻遍 classpath。这个议题真正把重心放到了"自动找出可用调用链"上。[13]

同年的《Breaking Parser Logic: Take Your Path Normalization off and Pop 0days Out!》则聚焦 path normalization 这一类攻击面,覆盖了包括 Java 在内的多种 Web framework。[13:1]

把这两项研究并置来看很有意思:一个关注对象图内部的调用关系,另一个关注代理、容器与 framework 对同一条路径的不同理解。它们指向的都不是某个单点的危险函数,而是系统各组件之间没有遵循同一套语义。

到了 Black Hat Europe 2019,《New Exploit Technique In Java Deserialization Attack》依旧在列。至少从议题的延续性可以看出,Java deserialization 绝非在 2016 年火过一阵便偃旗息鼓。[14]

四、2021:Log4Shell 把积累多年的问题一次性引爆

CVE-2021-44228,即人们熟知的 Log4Shell。NVD 的漏洞描述指出,受影响的 Log4j 2 版本中,JNDI 功能没有对攻击者可控的 LDAP 等 JNDI endpoint 做出正确防护,而攻击入口可以来自 configuration、log message 甚至普通 parameter。[15]

CISA 随后指出,Log4j 被集成进了全球数千种产品。[16] 这也解释了为什么当时许多公司最棘手的工作并非编写 WAF 规则,而是先要回答一个更基础的问题:生产环境里究竟有哪些系统带着这个版本的 Log4j。

Log4Shell 并非凭空冒出的全新手法,它真正做的,是把几条早已存在多年的研究线索汇到了一处:

  1. 普通业务输入进入日志;
  2. 日志组件把字符串当成可解释的内容;
  3. Lookup 机制触发 JNDI;
  4. 服务端允许访问外部 LDAP 等目标;
  5. 组件运行在高权限的 Java 进程里;
  6. Log4j 又被大量产品间接打包、层层二次封装。
普通业务输入进入日志后被当作可解释内容,经 Lookup 触发 JNDI,服务端出网加载恶意类,在高权限进程内执行;供应链的层层封装则把影响面放大到全球数千种产品。
图 4:Log4Shell —— 多条研究线索的汇流与供应链放大

2016 年的 JNDI 研究点明了这项能力的危险所在,而 2021 年的 Log4Shell 则用现实证明:一个底层通用库一旦把普通输入接到这项能力上,波及面会大到什么程度。

它也暴露了 Java 生态长期存在的三个管理难题:

  • 依赖可能藏在 Fat JAR、WAR、EAR、插件目录或厂商产品里;
  • 构建文件里的依赖版本不一定等于运行时真正加载的版本;
  • 即使应用代码没有直接使用某个功能,间接依赖仍可能把它带进来。

五、2023—2025:工具不再只找入口,还要证明整条链跑得通

ODDFuzz 把静态分析和 directed greybox fuzzing 接了起来

Black Hat USA 2023 的《ODDFuzz: Hunting Java Deserialization Gadget Chains via Structure-Aware Directed Greybox Fuzzing》继续解决 gadget chain 自动发现问题。[17]

ODDFuzz 先做轻量级 static taint analysis,找出候选 gadget chain;再使用 structure-aware seed generation 和 directed greybox fuzzing 生成能够触发候选链的序列化对象。它的目标很明确:静态分析负责尽量别漏,动态验证负责压低误报。[18]

论文给出的实验结果是,在 ysoserial 数据集中,它发现了 34 条已知链中的 16 条,两种对比方案只能找到 3 条;在 WebLogic、Dubbo、Nexus 和 protostuff 等真实目标中,研究还发现了 6 条此前未报告的可利用链,并获得 5 个 CVE。[18:1]

这里最关键的进步,并不在于数字,而在于研究标准被抬高了。

仅仅找到 readObject() 已经不够,仅仅确认 source 到 sink 可达也不够。工具开始尝试构造真实的对象,去满足字段关系、类型约束和运行时分支,最终证明这条链确实能够被触发。

JDD 继续解决 path explosion 和 payload construction

Black Hat Asia 2025 的 JDD 议题全名是《In-depth Mining of Java Deserialization Gadget Chains via Bottom-up Gadget Search and Dataflow-aided Payload Construction》。[19]

JDD 项目先把调用关系总结成 gadget fragment,再使用 bottom-up search 连接候选片段,同时借助 dataflow 关系辅助 payload construction。项目论文发表于 2024 IEEE Symposium on Security and Privacy,工具支持 JDK、Hessian、JSON 等多种 deserialization protocol。[20]

从 2018、2023 再到 2025,这条工具的演进路线相当清晰:

  • 2018 年,重点是自动找出调用链;
  • 2023 年,重点是用结构感知 fuzzing 验证候选链;
  • 2025 年,重点是压制 path explosion,并解决对象字段关系与 payload construction。

Java 反序列化研究,已经从"是否存在危险 API"迈入了"能否自动产出一条可验证攻击链"的阶段。

从 2014 年 RAVAGE 的运行时数据流,到 2018 年 Gadget Inspector 自动找链、2023 年 ODDFuzz 结构感知验证、2025 年 JDD 自底向上搜索与 payload 构造,研究标准从找到入口逐步走向证明可利用。
图 5:反序列化利用链挖掘工具的能力演进(2014—2025)

六、2026:新方向是语义差异、中间件的认证前缺陷与 JVM 内存马

2026 年的 Black Hat Java 议题不再只围绕反序列化打转,而是冒出了三个格外值得关注的方向。

Cast Attack:同一份输入,在字符层和字节层被读成两种含义

Black Hat Asia 2026 的《Cast Attack: A New Threat Posed by Ghost Bits in Java》研究了 Java 类型转换中的数据丢失,以及这种丢失如何催生出新的攻击面。[21]

Oracle 的 Java 26 API 文档对 DataOutputStream.writeBytes(String) 说得相当直白:字符串里的每个字符在写出时,高 8 位都会被直接丢弃。[22]

这里的风险,并不在于"类型转换会报错"——恰恰相反,它往往悄无声息,根本不报错。设想安全设备、校验逻辑或审计日志读到的是原始的 Unicode String,而后端组件最终取用的却是丢弃高位后的低 8 位字节,那么这两层对同一份输入的理解,就出现了分歧。

这类问题可以归入 parser differential 或 representation differential:安全检查依据的是一种表示,真正执行依据的却是另一种表示。此时只针对某个 payload 的特征打补丁往往治标不治本,开发者真正需要审查的,是从编码到类型转换的完整链条。

以字符 U+012F 为例,安全层看到的是非 ASCII 字符,不视其为路径分隔符;writeBytes 丢弃高字节 0x01 后只剩低字节 0x2F 即斜杠,执行层因此拿到一个可用于路径穿越的字符,前后两层理解不一致。
图 6:Cast Attack —— 高 8 位丢弃导致的字符层与字节层表示差异

对 Java 代码来说,下面几类写法都值得专门审计:

  • charbyte 的 narrowing conversion;
  • writeBytes(String) 写协议、路径或命令数据;
  • 未明确指定 Charset 的字符串和字节转换;
  • 校验发生在编码之前,而危险操作发生在编码之后;
  • WAF、Servlet container 和业务框架使用不同的 decode 规则。

企业级 Java:pre-auth RCE 依旧潜伏在中间件里

Black Hat USA 2026 的《Pre-auth RCE in Enterprise Java: When Middleware Becomes the Exploit》把视线重新拉回企业级 Java 平台。[23]

研究团队的公开文章称,他们审计了 4 个企业级 Java 平台,发现 12 个漏洞,其中包括 1 个 sandbox escape 和 4 个 pre-auth 问题,并展示了 Bonita BPM 与 Apache OFBiz 中两条完整、相互独立的 pre-auth RCE chain。[24]

这批问题没有依赖某个单一“万能 gadget”。共同模式是:

  • 外部请求先到达公开入口;
  • router、security filter、servlet dispatcher 或 SSO handler 对请求的判断不一致;
  • 原本只对内部组件开放的接口,被未认证请求直接摸到;
  • deserializer、template engine 或其他 execution sink 把可达性变成代码执行。

这类漏洞最容易被漏掉,因为单看每个组件,设计都挑不出毛病。router 以为 filter 会拦下来,filter 以为 dispatcher 不会这么解析,内部 API 又默认调用方早已通过认证。真正的漏洞,恰恰出现在这些彼此错位的假设被拼接到一起之后。

JVM 内存马:拿到 RCE 之后,运行时状态本身也成了研究对象

Black Hat Asia 2026 还有《More JVM Memory Shells: JVM Memory Shell Auto Searching Program》。官方日程将它归入 Malware 和 Threat Hunting;研究者介绍的是一个结合 static analysis、runtime instrumentation 和 in-memory inspection 的自动化框架,用来发现 JVM 内存马。[21:1][25]

JVM 内存马的棘手之处在于,它未必对应磁盘上的某个新文件。攻击者可能改写已经加载的 class,注册新的 Filter、Listener、Servlet,挂接框架的 handler,或是借助 Java Agent 与 instrumentation 悄悄改变运行时行为。

因此,只做文件扫描和依赖扫描远远不够。现代 Java 防守还必须掌握:

  • JVM 里实际加载了哪些 class;
  • 哪些 class 在运行中被重新定义;
  • Web 容器里的 Filter、Listener、Servlet、handler 列表是否发生变化;
  • 是否出现异常 ClassLoader、Java Agent、线程或动态字节码;
  • 运行时状态是否与部署基线一致。

这一方向表明,Java 安全研究的视野,已经从"如何拿到代码执行"延伸到了"如何发现 JVM 内部的驻留与篡改"。

七、从这些议题里,能读出六个趋势

1. 安全边界从 JVM 内部扩展到了整个系统

2002 年的核心问题是 bytecode verifier 和 sandbox 能不能挡住不可信代码。2026 年的核心问题已经变成:一条请求经过 WAF、router、Servlet container、framework、serializer 和内部服务后,哪一层把它理解错了。

今天审计 Java 应用,至少要把下面这些东西放在一张图里:

  • JDK 和 JVM 参数;
  • framework 与 middleware;
  • classpath 和插件;
  • reverse proxy 与 API gateway;
  • 消息队列和 RPC;
  • 网络出口;
  • 容器、云身份和运行时状态。

只看业务 Controller,通常看不完整。

2. memory safety 不是 Java 安全的终点

Java 语言减少了很多 use-after-free、buffer overflow 和手工内存管理问题,但严重漏洞仍然可以来自 reflection、deserialization、JNDI、expression language、template engine、ClassLoader、类型转换和路径解析。

这些问题所利用的,恰恰是合法的语义。攻击者无需把 JVM 打崩,只需诱导系统按一个危险却"合规"的顺序,去调用那些本就存在的功能。

3. 最危险的地方,往往在组件与组件的衔接处

path normalization、Cast Attack 以及 2026 年中间件的 pre-auth RCE,都指向同一个共性:不同组件对同一份输入,并没有达成一致的理解。

这类解读落差常见于:

  • 原始 URL 与规范化后的 URL;
  • Unicode 字符串与最终落地的字节;
  • proxy path 与 Servlet path;
  • public API 与 internal API;
  • JSON 里的类型信息与 Java 实际加载的类;
  • 构建期的依赖与运行时的 classpath。

单个组件各自通过了测试,并不代表它们组合起来依然安全。

一条请求依次穿过 WAF、反向代理、Servlet 容器、框架与序列化层,再到内部服务;每两层之间的解读落差构成薄弱点,攻击往往就发生在这些交界处,单层测试无法覆盖。
图 7:组件衔接处 —— 同一请求被逐层重新理解

4. 漏洞挖掘从“找到可疑点”走向“证明可利用”

RAVAGE、Gadget Inspector、ODDFuzz、JDD 这一类工作都在解决同一个现实问题:静态工具给出一条 source-to-sink 路径很容易,证明它在真实 classpath、真实对象状态和真实 framework 生命周期里能够触发,要难得多。

接下来的高价值工具会更关注:

  • 对象和字段约束;
  • reflection、proxy 和 callback;
  • framework lifecycle;
  • 配置与依赖版本;
  • 动态验证;
  • 最小可复现输入;
  • 运行时副作用确认。

5. Java 供应链问题最终会落到运行时

Log4Shell 之后,SBOM 和 Software Composition Analysis 成了热门话题,它们固然重要,但 Java 很容易陷入"构建时看到的是一套,运行时加载的却是另一套"的尴尬。

Fat JAR、WAR、应用服务器共享目录、插件、Java Agent、容器镜像和厂商封装都会改变实际 classpath。真正有用的资产清单,必须能回答运行中的 JVM 加载了什么,而不是只回答 pom.xml 写了什么。

6. 老问题和新问题会长期共存

2026 年仍在研究企业级 Java 的 pre-auth RCE 和 JVM 内存马,恰恰说明老旧中间件、旧协议和 Java Serialization 远未退出生产环境;与此同时,Cast Attack 又把注意力引向了字符与字节的表示差异。

Java 生态的特点,从来不是某个时代彻底取代上一个时代,而是新旧攻击面层层叠加。一个现代的 Spring 应用,仍可能依赖十多年前设计的 serialization protocol,跑在更早的应用服务器上,外面再套一层新的 API gateway。

八、现在做 Java 安全,应该重点检查什么

1. 先减少反序列化入口,再谈 filter

跨信任边界尽量不要使用 Java Object Serialization。HTTP、消息队列、缓存、RPC 和文件导入都应使用结构明确的数据模型,并限制可实例化类型。

无法立即移除时,可以使用 JEP 290 的 ObjectInputFilter 限制 class、array size、object graph depth、reference count 和 stream size;JEP 415 又增加了 context-specific 和动态选择 filter 的能力。[26][27]

但 filter 是补偿措施,不是免死金牌。还要检查当前 classpath 中是否存在可利用 gadget,并确认 framework 有没有自己创建 ObjectInputStream 绕开全局配置。

2. JNDI 名称和 Provider URL 不能由外部输入决定

重点搜索并跟踪:

JAVA
InitialContext.lookup(name);
Context.lookup(name);
JndiTemplate.lookup(name);

不要只看参数是不是“看起来像固定前缀”。需要继续检查字符串拼接、环境变量、Header、消息属性、数据库配置和目录内容能否影响最终 lookup。

网络侧应默认限制 Java 服务访问外部 LDAP、RMI、CORBA 和不必要的 DNS 目标。很多动态加载链需要服务端主动出网,egress control 能直接截断这一环。

3. 校验和使用必须基于同一份规范化结果

路径、URL、Header、Unicode 和协议字段应该在入口处完成一次明确转换。鉴权、路由、日志和业务逻辑尽量使用同一个 canonical representation。

涉及字符串转字节时,明确指定 Charset,避免把 writeBytes(String) 用在安全敏感数据上,也不要在校验后再做会丢信息的 narrowing conversion。

测试时要跑真实部署组合,包括 CDN、WAF、reverse proxy、Servlet container 和 framework。只在单元测试里调用 Controller,发现不了 parser differential。

4. internal API 也要有自己的认证和授权

不要把“外部路由访问不到”当作内部接口的唯一保护。内部 API、management endpoint、serializer、template engine 和 script executor 应在自身入口做认证、授权和输入约束。

如果组件确实只允许内部调用,最好使用独立 listener、网络 namespace、mTLS 或 workload identity,而不是只靠一个 path prefix。

5. 依赖清单要覆盖运行时 classpath

除了 SCA 和 SBOM,还应定期采集:

  • JVM 实际加载的 JAR 和 module;
  • Fat JAR、WAR、EAR 内嵌依赖;
  • 应用服务器共享 library;
  • plugin 和 Java Agent;
  • 容器基础镜像里的重复版本;
  • 同名 class 的实际加载来源;
  • 动态下载和缓存的 artifact。

漏洞响应时,能快速回答“哪些 JVM 真的加载了受影响 class”,比只搜索 Git 仓库更有用。

6. 给 JVM 运行时做基线

生产环境可以记录 class loading、ClassLoader、Java Agent、Filter、Listener、Servlet、handler、线程和关键 system property 的基线。

发现下面这些变化时应触发调查:

  • 未知 Java Agent attach;
  • class retransformation 或 redefinition;
  • 新增的 Filter、Listener、Servlet;
  • 异常 ClassLoader;
  • 只存在内存中、无法对应部署 artifact 的 class;
  • 可疑线程和定时任务;
  • framework registry 在运行中发生变化。

7. 把 RCE 后的权限压到最低

Java 进程不应默认拥有写应用目录、执行系统命令、访问云元数据、读取全量 Secret 或连接全部内网服务的权限。

更实际的防线包括:

  • 独立低权限操作系统用户;
  • read-only filesystem;
  • 最小化容器镜像;
  • 网络出口 allowlist;
  • 最小权限 ServiceAccount 和云身份;
  • 敏感管理面与业务面隔离;
  • 数据库、消息队列和制品仓库使用短期凭据。

Java 层漏洞不可避免时,这些控制决定一次 RCE 最终能走多远。

结语

2002 年,研究者还在琢磨如何骗过 bytecode verifier 和 Applet sandbox。2016 年之后,注意力转向了 Java Serialization、JNDI 和企业中间件。而到了 2026 年,研究重点又延伸到字符与字节之间的语义差异、中间件认证前的解析分歧,以及 JVM 运行时中的内存马驻留。

表面上看,漏洞类型换了很多轮,核心问题其实一直很稳定:

谁在解释不可信输入,解释过程中调用了哪些动态能力,最后又拿到了什么权限。

Java 的强项在于成熟、兼容与可扩展,而它的安全难点也恰恰源于同一处:大量历史机制、动态能力和企业组件,长年累月地叠加在一起。今天想做好 Java 安全,既不能只盯着某一个 CVE,也不能指望靠升级 JDK 就一劳永逸。真正需要管理的,是完整的数据链路、运行时的 classpath、组件之间的解释差异,以及代码执行之后的权限边界。

这也是 Black Hat 二十余年 Java 议题留下的最清楚的一条线索。


参考资料