简介
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定义的类型。
测试环境
java version “1.8.0_71”
pom.xml
1 | <dependency> |
示例
随手写一个简单的JavaBean类
1 | public class Person { |
SnakeYaml提供了Yaml.dump()和Yaml.load()两个函数对yaml格式的数据进行序列化和反序列化。
- Yaml.load():将yaml转换成java对象
- Yaml.dump():将一个对象转化为yaml;
1 | import org.yaml.snakeyaml.Yaml; |
可以发现当不存在某个属性,或者存在属性但不是由public修饰的时候,序列化会调用其getter方法,反序列化时会调用其setter方法。
序列化的结果前面的
!!
是用于强制类型转化,强制转换为!!
后指定的类型,其实这个和Fastjson的@type
有着异曲同工之妙。用于指定反序列化的全类名。
利用原理
到这里就会发现和fastjson似乎有异曲同工之妙了,这里会调用setter方法导致安全隐患,于是fastjson的蛮多链子也可以套用起来
利用链
JdbcRowSetImpl利用链
这里和fastjson的触发一致,都是触发setAutoCommit()方法,调用connect函数,然后触发InitialContext.lookup(dataSourceName),而dataSourceName可以通过setDataSourceName可控
1 | import org.yaml.snakeyaml.Yaml; |
Spring PropertyPathFactoryBean利用链
这个链子需要springframework依赖
1 | import org.yaml.snakeyaml.Yaml; |
这里利用setBeanFactory()
方法
1 | public void setBeanFactory(BeanFactory beanFactory) { |
这里可以调用到任意类的getBean()方法,然后利用org.springframework.jndi.support.SimpleJndiBeanFactory#getBean()
触发JNDI注入
1 | public <T> T getBean(String name, Class<T> requiredType) throws BeansException { |
这里需要调用到getBean()
方法,首先要满足isSingleton(this.targetBeanName)
返回值为false
1 | public boolean isSingleton(String name) throws NoSuchBeanDefinitionException { |
this.shareableResources是一个HashSet对象,也就是利用setter方法设置this.shareableResources包含this.targetBeanName即可
C3P0利用链
在C3P0利用链中提到了基于fastjson进行JNDI注入和反序列化利用
同理也可以套用在snakeyaml链上
JNDI注入
1 | import org.yaml.snakeyaml.Yaml; |
反序列化
1 | import org.yaml.snakeyaml.Yaml; |
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
文件,文件内容指定接口的实现类。
1 | import org.yaml.snakeyaml.Yaml; |
具体执行细节可以参考https://www.cnblogs.com/nice0e3/p/14514882.html#%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90
参考链接:
Java安全之SnakeYaml反序列化分析 | nice_0e3