SnakeYaml 反序列化分析
前言
继续学习Yaml反序列化,SnakeYaml是Java中用来解析Yaml格式的库,可用于Java对象的序列化和反序列
反序列化分析
先导入SnakeYaml的依赖
|
|
接着构造一个User类,试着将这个User类通过yaml进行转换,看一下转换后的效果,其中Yaml中有两个重要的函数:
- load() :解析传进来的参数,生成相应的Java对象
- dump() :将Java对象转换为yaml格式
输出结果中,!! 表示强制类型转换,强制转换为User类型
接着将转换的内容进行load()操作,在yaml.load(str)
处打断点进行分析
|
|
进入到org.yaml.snakeyaml.Yaml#loadFromReader
中,其中str放入到StreamReader
中,type为传进来的固定的值Object
,经过一系列的设置进入到getSingleData
中
会先通过getSingleNode()
获取一个Node
实例,其中有一个tag属性其值表示为会转换成一个User类型的对象,然后判断node和tag属性是否为空,然后继续判断type是否为Object和根标签是否为空,最后将node传入到constructDocument()
中
可以看到data即为转换的User类,而data是在this.constructObject(node)
中得到的,继续跟进
接着在org.yaml.snakeyaml.constructor.BaseConstructor#constructObjectNoCheck
中,this.constructedObjects.containsKey(node)
为false然后进入到constructor.construct(node)
中获取data值
继续跟进,先进入到getConstructor
中
在getClassForNode
中,截取tag属性中的类名User并进行编码后,并将User赋值给Class cl
,然后返回,最后是将cl赋值给node的type属性
在construct
中,先是通过this.newInstance
创建了一个User实例,接着进入到this.constructJavaBean2ndStep
在constructJavaBean2ndStep
中,通过while循环在一开始传进来的字符串中提取value值,然后在property.set(object, value)
通过反射将value值赋值给创建的User类中,最后return给data返回创建好的User类
这样一来,如果对构造好的恶意的payload进行解析就有可能触发漏洞,这里拿JdbcRowSetImpl
类来示范一下,确实触发了JNDI注入弹出计算器
这里注意一点,jdk版本需要满足 8u161 < jdk < 8u191
SPI机制
看了不少的文章都在Yaml反序列时使用了SPI机制去触发弹计算器,这里也记录一下这个知识点(好记性不如烂笔头)
SPI(Service Provider Interface),是JDK内置的一种 服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver接口,而Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是解耦
整体机制如下:
当服务的提供者提供了一种接口的实现之后,需要在classpath下的META-INF/services/
目录里创建一个以服务接口命名的文件(文件名为:javax.script.ScriptEngineFactory),这个文件里的内容就是这个接口的具体的实现类。当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services/
中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。JDK中查找服务的实现的工具类是:java.util.ServiceLoader
也就是说可以构造一个恶意类,然后在web服务下的META-INF/services/
中的配置文件中指定这个恶意类(类需要继承ScriptEngineFactory接口),然后在yaml.load()中传入触发SPI机制的参数即可
|
|
在yaml解析的过程中最后实例化ScriptEngineManager
然后触发init()
,进行一系列赋值后进入到initEngines(loader)
一路跟进到最后在java.util.ServiceLoader.LazyIterator#nextService
中,得到要加载的类名,然后在service.cast(c.newInstance())
中对加载的恶意类进行实例化然后触发弹计算器
更多的Gadget可参考Mi1k7ea师傅的文章
防御方法
开启new SafeConstructor()
即可防御Yaml反序列
|
|