
1. 项目概述为什么我们需要一张“底层图谱”干了这么多年安全研究和渗透测试我越来越觉得Java安全尤其是反序列化和JNDI注入这块就像一座巨大的冰山。新手看到的可能只是水面上的几个著名漏洞编号——Shiro-550、Fastjson 1.2.47、WebLogic CVE-2017-10271。但水面之下是错综复杂的类加载机制、动态代理、字节码操作、命名与目录服务接口以及它们之间千丝万缕的调用关系。很多人学这块知识都是跟着一个个漏洞复现文章“照葫芦画瓢”敲完命令看到弹了个计算器就觉得会了。但下次遇到一个没见过的组件或者一个变种利用链立刻就懵了因为脑子里没有一张完整的“地图”。这就是我写这篇“硬核梳理”的初衷。我不想再重复那些已经被写烂了的、步骤化的漏洞复现教程。我想做的是带大家亲手画一张属于你自己的“Java反序列化与JNDI注入底层图谱”。这张图里反序列化是触发漏洞的“发动机”JNDI注入是达成远程代码执行的“高速公路”而像Shiro、Fastjson、WebLogic这些组件则是这条高速公路上一个个关键的“收费站”或“服务区”。理解了这个拓扑关系你就能从“背漏洞”进化到“挖漏洞”和“防漏洞”。无论你是正在啃Java安全面试八股文的求职者还是想深入理解漏洞原理的开发工程师或是奋战在一线的安全研究员这张图谱都能帮你把零散的知识点串联起来形成一个稳固的、可推导的知识体系。接下来我们就从最底层的原理开始一步步把这幅图拼完整。2. 核心原理深度拆解发动机与高速公路要画好图谱必须先理解两个最核心的“零件”反序列化如何充当发动机JNDI又如何铺就高速公路。这部分可能有点烧脑但我会尽量用生活中的例子帮你理解。2.1 反序列化漏洞的“万能发动机”你可以把Java反序列化想象成一个“乐高说明书重建器”。你有一盒乐高零件序列化后的字节流和一份说明书对象的类定义。ObjectInputStream.readObject()这个方法就是按照说明书把零件重新拼成原来的乐高模型对象实例的过程。问题的核心在于这份“重建说明书”太强大了。它在重建对象时并不是简单地给属性赋值而是会主动去调用对象的一些特定方法readObject(ObjectInputStream in)如果类自定义了这个方法反序列化时会优先调用它。这是攻击者最爱的“后门”他们可以在这个方法里写入任意逻辑。readResolve()在readObject之后调用用于替换反序列化生成的对象。Getter/Setter方法对于某些序列化库如Fastjson在反序列化属性时会通过反射调用对应的setter或getter方法。漏洞的发动机如何点火攻击者精心构造一个序列化数据恶意的“乐高零件组合”这个数据在被反序列化时会触发目标类里上述某个方法的执行。这个方法里的代码可能就是攻击者想要执行的恶意操作的第一步。但通常这一步还做不到直接执行系统命令它需要找到一个“跳板”把执行权传递出去。这个跳板往往就是JNDI。实操心得理解“利用链”单个类很难完成复杂的恶意操作。因此实战中都是利用一条“链”Gadget Chain。比如A类的readObject方法里调用了B类的方法B类的方法里又去加载了C类……最终指向一个能执行代码的“危险函数”如Runtime.exec()。寻找和组合这些类就是构造反序列化利用链的核心工作。Apache Commons Collections库之所以“著名”就是因为它里面提供了大量符合这种链式调用的类。2.2 JNDI注入通往恶意代码的“高速公路”JNDIJava Naming and Directory Interface本身是个好东西它是个抽象层让Java应用可以用统一的方式访问各种命名和目录服务比如LDAP、RMI、DNS、文件系统等。你可以把它理解成一个“服务查询总线”。正常的流程是应用通过JNDI API如InitialContext.lookup(“ldap://example.com/obj”)发起一个查询JNDI SPI服务提供者接口会根据URL协议ldap://找到对应的服务驱动如LDAP服务驱动然后去指定的服务器example.com获取一个对象引用。漏洞的“高速公路”如何通车JNDI注入漏洞就出在这个lookup()的参数即URL是攻击者可控的。如果应用未经校验就直接使用用户输入进行JNDI查询攻击者就可以让它查询一个由自己控制的恶意服务。更致命的是JNDI协议支持“对象引用”的返回。对于RMI和LDAP协议客户端在查询时服务器可以返回一个“Reference”对象。这个对象告诉客户端“你要的对象不在我这儿你去http://attacker.com/evil.class这个地址加载吧”。而Java在默认情况下尤其是历史版本中会自动去加载这个远程的class文件并实例化。这个class文件的构造方法或静态代码块里的恶意代码就会被执行。关键点自动加载的触发条件RMI当RMI Registry返回一个Remote对象时客户端会进行反序列化。如果返回的是Reference则会触发远程class加载。LDAPLDAP服务端可以返回一个携带javaCodeBase和javaClassName的Reference条目直接触发客户端远程加载。注意事项版本差异与绕过高版本JavaJDK 6u132, 7u122, 8u113之后默认限制了远程类加载。com.sun.jndi.rmi.object.trustURLCodebase和com.sun.jndi.ldap.object.trustURLCodebase等属性默认为false。但这不等于JNDI注入失效只是攻击面发生了变化。后续出现了多种绕过方式例如利用本地ClassPath中的类寻找目标应用ClassPath中已有的、可利用的类如Tomcat中的org.apache.naming.factory.BeanFactory来构造Reference Factory进行EL表达式注入或反序列化。利用其他协议如DNS协议进行信息泄露辅助漏洞探测。结合其他反序列化点将JNDI注入作为反序列化利用链中的一个环节通过链式调用最终触发InitialContext.lookup。3. 经典组件漏洞拓扑解析理解了发动机和高速公路现在我们来看看几个著名的“收费站”是如何被攻破的。我会用拓扑图的思维来分析展示漏洞触发的完整路径。3.1 Apache ShiroRememberMe的反序列化之旅Shiro的漏洞核心在于其身份认证机制。为了提供“记住我”功能Shiro会将用户的认证信息序列化后用AES加密存储在Cookie的rememberMe字段中。当用户再次访问时Shiro会解密这个Cookie并反序列化以恢复用户会话。漏洞触发拓扑用户请求携带RememberMe Cookie - Shiro Filter拦截 - 使用固定或泄漏的AES密钥解密 - 得到序列化数据 - 调用ObjectInputStream.readObject() - 触发反序列化利用链 - 执行恶意代码。关键攻击面密钥硬编码或泄漏Shiro-550早期版本使用默认硬编码的AES密钥。如果开发者未修改攻击者可以直接加密恶意序列化数据构造合法的RememberMe Cookie。Padding Oracle攻击Shiro-721即使密钥未知由于Shiro在解密时使用了CBC模式且错误处理不当攻击者可以通过旁路攻击根据服务器返回的错误差异逐步爆破出明文和密钥进而伪造Cookie。Shiro与JNDI的联动单纯的Shiro反序列化利用链可能受限于目标ClassPath中的gadget。结合JNDI注入利用链的构造就灵活多了。一个典型的链式攻击思路是利用Shiro反序列化执行一个“启动器”这个启动器的唯一作用就是去执行一次new InitialContext().lookup(attacker-controlled-url)从而转向更强大的JNDI注入攻击流程。实操心得Shiro漏洞检测与利用检测使用dnslog平台测试。发送一个精心构造的、指向dnslog.cn子域的RememberMe Cookie如果dnslog收到解析记录说明存在反序列化漏洞且可能命令执行成功因为通常利用链会触发DNS查询。利用工具shiro_attack等工具集成了密钥爆破、利用链检测和回显内存马注入等功能是实战利器。内存马这是目前最主流的利用方式。不再追求弹一个Shell而是通过反序列化或JNDI注入在目标JVM中动态注册一个Filter或Servlet内存马实现持久化、无文件的后渗透控制。Godzilla、Behinder等冰蝎/哥斯拉木马的管理端都支持生成相应的内存马Payload。3.2 Fastjson基于属性触发的JNDI注入Fastjson的漏洞原理与标准的Java反序列化有本质区别。它并非直接调用readObject而是通过反射在解析JSON字符串、为Java对象属性赋值时自动调用该属性的setter方法或者在解析特定字段时调用getter方法。漏洞触发拓扑以经典漏洞为例攻击者提交恶意JSON - Fastjson解析尝试实例化JdbcRowSetImpl类 - 为dataSourceName属性赋值触发setDataSourceName() - 该方法内部调用了setAutoCommit() - setAutoCommit()中触发了connect() - connect()方法中进行了 InitialContext.lookup(this.dataSourceName) 调用 - lookup参数可控触发JNDI注入。关键攻击面AutoType特性Fastjson为了安全默认关闭了基于type指定任意类进行反序列化的功能AutoType。但历史上出现过多次AutoType绕过例如利用黑名单遗漏、异常抛出、类继承关系等。利用链依赖即使开启了AutoType或存在绕过也需要目标ClassPath中存在可用的利用链类如JdbcRowSetImpl。这类类通常存在于tomcat-dbcp,commons-dbcp等数据库连接池库中。Fastjson与JNDI的深度绑定Fastjson的很多RCE漏洞最终都是通过触发JNDI注入来实现的。JdbcRowSetImpl只是最著名的一条链。攻击者构造的JSON中type指定为com.sun.rowset.JdbcRowSetImpldataSourceName设置为一个恶意的LDAP/RMI地址。Fastjson在反序列化过程中通过属性设置一步步将执行流导向InitialContext.lookup从而完成攻击。注意事项Fastjson版本与绕过Fastjson的修复和绕过是一个长期的攻防过程。从1.2.24到1.2.80几乎每个大版本都有严重的漏洞或绕过出现。在代码审计和漏洞排查时单纯升级版本未必绝对安全必须结合代码中Fastjson的使用方式是否开启AutoType是否使用安全模式SafeMode进行综合判断。对于开发者最安全的做法是使用JSON.parseObject(jsonString, User.class)这种指定具体目标类的方式避免泛型解析。3.3 Oracle WebLogic协议处理中的反序列化点WebLogic作为重量级Java应用服务器其漏洞往往出现在它丰富的特性和服务中例如T3、IIOP协议以及Coherence、WLS Security等子组件。漏洞触发拓扑以T3协议为例攻击者构造恶意序列化数据 - 通过T3协议发送至WebLogic Server的7001端口 - WebLogic的T3协议实现weblogic.rjvm.InboundMsgAbbrev在解析数据时进行反序列化 - 触发利用链常涉及ObjectInputStream、ClassLoader等 - 执行恶意代码。关键攻击面T3/IIOP协议这是WebLogic独有的高性能RPC协议。为了传输对象协议中内置了反序列化操作。攻击者可以直接向服务端口发送恶意的序列化数据包。XMLDecoderWebLogic的wls9-async、wls-wsat等组件在处理SOAP等XML消息时使用了不安全的XMLDecoder来解析数据导致反序列化漏洞如CVE-2017-10271。Coherence组件WebLogic内置的Coherence缓存库其Filter等接口的反序列化处理存在漏洞如CVE-2020-2555。WebLogic漏洞的综合性WebLogic的漏洞常常是“复合型”的。例如一个漏洞可能先通过T3协议触发反序列化反序列化利用链中再包含一个JNDI注入的调用最终通过加载远程恶意类完成攻击。它的ClassPath非常庞大包含了大量第三方库如Apache Commons Collections, Spring等这使得攻击者可以组合出极其复杂的利用链。实操心得WebLogic漏洞利用与防御利用工具化程度高ysoserial等工具集成了针对不同CVE的Payload生成。通常使用java -jar ysoserial.jar CommonsCollections1 command生成Payload然后通过脚本封装成T3协议包发送。防御最有效在防火墙层面阻断外网对WebLogic T37001、IIOP等端口的访问仅限内网管理节点通信。升级与补丁及时安装官方补丁。但需注意一些漏洞的补丁可能被绕过。访问控制删除或禁用不必要的组件如wls9_async、wls-wsat。4. 漏洞挖掘与防御实战视角知道了原理和案例我们如何主动发现这类漏洞又如何有效地防御呢这部分是从“看图”到“画图”和“修路”的关键。4.1 攻击者视角如何挖掘反序列化与JNDI注入点入口点发现信息收集功能点探测关注任何接收序列化数据的功能如文件上传、网络通信、缓存数据读取、Cookie处理特别是rememberMe、JSESSIONID等、RPC接口。组件识别通过报错信息、URL路径、响应头等识别目标使用的组件及其版本Shiro, Fastjson, WebLogic, Jackson, XStream等。流量分析拦截HTTP/HTTPS请求观察参数中是否包含Base64编码的类Java序列化数据特征以rO0A开头或JSON数据中是否包含type等特殊字段。漏洞验证与利用链构造黑盒测试使用DNSLog等带外检测技术。向疑似入口点提交一个会触发DNS查询的Payload如URLClassLoader加载一个远程URL观察是否有回连。白盒/灰盒审计搜索代码中的ObjectInputStream.readObject()、XMLDecoder.readObject()、JSON.parseObject()、InitialContext.lookup()等危险方法。分析这些方法的参数是否用户可控。分析项目的依赖库pom.xml,gradle看是否存在已知漏洞的组件版本如commons-collections 3.1, fastjson 1.2.83。利用链挖掘这是一个深度技术活通常需要分析目标ClassPath中的所有jar包。使用静态分析工具如GadgetInspector辅助寻找从readObject/getter/setter到危险函数Runtime.exec,Method.invoke,ClassLoader.loadClass等的调用路径。重点关注实现了Serializable接口的类以及它们的继承关系和成员变量。4.2 防御者视角构建纵深防御体系防御不是简单升级一个库而是一个系统工程。表反序列化与JNDI注入防御策略矩阵防御层面具体措施原理与说明代码开发层1.避免反序列化不可信数据这是根本。如无必要不使用Java原生序列化。切断漏洞源头。对于必须序列化的场景使用JSON、Protobuf等更安全的格式。2.使用安全的反序列化库Jackson配置enableDefaultTyping为falseFastjson使用SafeMode或指定具体类。限制反序列化过程中可实例化的类范围。3.校验lookup()参数对InitialContext.lookup()的参数进行严格的白名单校验。防止JNDI注入。4.自定义ObjectInputStream重写resolveClass()方法进行严格的类名白名单校验。这是防御Java原生反序列化最有效的手段之一。依赖与环境层1.升级JDK版本使用JDK 8u191/11.0.1/7u201/6u211及以上版本默认关闭JNDI远程类加载。封堵高版本JNDI注入的直接远程代码执行。2.升级第三方库及时更新Commons-Collections、Fastjson、Shiro等组件到安全版本。修复已知漏洞的利用链。3.使用Security Manager配置严格的Java安全策略限制敏感操作。增加攻击难度但配置复杂对性能有影响。4.JVM参数加固添加-Dcom.sun.jndi.rmi.object.trustURLCodebasefalse-Dcom.sun.jndi.ldap.object.trustURLCodebasefalse等参数。显式关闭高风险特性即使在新版本中也建议加上。运行时与运维层1.WAF/IPS规则部署规则拦截包含序列化魔术头、type特征、JNDI协议ldap://,rmi://的请求。网络层拦截已知攻击特征。2.RASP防护在应用内部监控危险方法的调用如Runtime.exec,ClassLoader.defineClass,InitialContext.lookup进行实时阻断。从应用内部进行深度防御能防御未知漏洞。3.最小化网络暴露如非必要WebLogic等中间件的T3/IIOP管理端口不应暴露在公网。减少攻击面。4.持续监控与审计监控服务器的异常网络连接特别是出向的LDAP/RMI连接、异常进程创建、以及应用日志中的异常报错。及时发现入侵行为。实操心得自定义ObjectInputStream白名单校验示例这是你在代码中可以直接使用的“银弹”之一。public class SafeObjectInputStream extends ObjectInputStream { private static final SetString WHITELIST Set.of( “com.example.dto.User”, “com.example.dto.Order”, // ... 其他允许的类 ); public SafeObjectInputStream(InputStream in) throws IOException { super(in); } Override protected Class? resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { String className desc.getName(); if (!WHITELIST.contains(className)) { throw new InvalidClassException(“Unauthorized deserialization attempt for class: ”, className); } return super.resolveClass(desc); } }使用方式用new SafeObjectInputStream(inputStream)代替new ObjectInputStream(inputStream)。这份白名单需要根据你的业务需求精心维护。5. 高级话题与未来演进掌握了基础我们可以看看更深处和更前沿的东西。5.1 不出网利用与内存马技术随着防御的加强如网络隔离、JDK高版本限制传统的“反序列化 - JNDI加载远程类 - 执行命令”的“出网”利用方式受阻。“不出网利用”和“内存马”成为主流。不出网利用利用链的最终目标不再是加载远程类而是直接利用目标ClassPath中已有的类通过复杂的反射和字节码操作在内存中构造出恶意功能。例如利用TemplatesImpl类动态定义字节码或利用BCEL ClassLoader加载内部字节码。内存马这是不出网利用的终极体现。攻击者通过反序列化或JNDI注入向目标Web容器的运行时动态注册一个恶意的Filter、Servlet、Controller或Listener。这个马没有文件落地存活于JVM内存中与正常应用组件无异极难通过常规文件查杀发现。检测内存马需要分析JVM中已加载的类、Filter映射链等运行时信息。5.2 自动化工具与攻防演进攻防两端都在走向自动化。攻击方ysoserial、marshalsec、JNDI-Injection-Exploit等工具提供了成熟的利用链和攻击向量。GadgetInspector等静态分析工具能辅助挖掘新的利用链。冰蝎、哥斯拉等Webshell管理平台内置了各种框架的内存马注入模块。防御方RASP运行时应用自保护技术日趋成熟能够在应用内部监控和阻断反序列化、类加载、JNDI查询等危险行为。基于Agent的Java安全产品可以实时分析字节码发现内存马注入行为。未来的对抗将更集中在利用链的绕过与检测、内存马的攻防以及基于AI/机器学习的异常行为识别上。对于安全从业者而言理解底层图谱的价值就在于无论攻击手段如何包装变化你都能看清其本质的驱动原理和连接路径从而做出有效的应对。