Yaml是一种“是一个可读性高而且容易被人类阅读,容易和脚本语言交互,用来表达资料序列的编程语言。”相似于XML但比XML更简洁,语法详见
http://www.ruanyifeng.com/blog/2016/07/yaml.html
⽬前有很多可以⽣成和解析YAML的第三⽅⼯具,常见的,如SnakeYaml,jYaml,Jackson等,不同工具的用法不同,今天分析一下SnakeYaml反序列化的原理以及反序列化漏洞产生原因。
快速开始
下载依赖:
<dependency> <groupId>org.yaml</groupId> <artifactId>snakeyaml</artifactId> <version>1.27</version> </dependency>
新建一个Test类:
public class Test { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
通过Yaml.dump序列化Test对象:
Test test = new Test(); test.setName("123"); Yaml yaml = new Yaml(); System.out.println(yaml.dump(test));
可以看到输出内容,SnakeYaml序列化后的格式为!!(类名){ 属性名称 :属性 }:

跟踪调试
然后通过yaml.load反序列化对象,下断点跟进一下反序列化的过程:

Yaml.load方法会先new一个StreamReader对象,将String类型转化为StreamReader,然后再去调用loadFromReader方法:


getSingleData先创建一个Node对象(其中调用getSingleNote()会根据流来生成一个文件,即将字符串按照yaml语法转为Node对象:

然后进行了判断node是否为空和判断node的tag是否为空,如果不为空那么判断传入的type是否为Object类型和是否有rootTag
if (node != null && !Tag.NULL.equals(node.getTag())) { if (Object.class != type) { node.setTag(new Tag(type)); } else if (this.rootTag != null) { node.setTag(this.rootTag); }
然后这里的条件都不满足,直接执行了constructDocument方法,继续跟进到getClassForNode方法:


继续跟进:constructObject中判断constructedObjects是否已经包含node,如果不包含就会执行constructObjectNoCheck方法:

Object data = this.constructedObjects.containsKey(node) ? this.constructedObjects.get(node) : constructor.construct(node);

这里调用了constructor.construct方法:
查看Construct类的实现,调用了getConstructor方法:

getConstructor方法调用了getClassForNode方法:

String name = node.getTag().getClassName();
getClassName方法先判断是否以tag:yaml.org,2002:开头,然后调用UriEncoder.decode方法:
public String getClassName() { if (!this.value.startsWith("tag:yaml.org,2002:")) { throw new YAMLException("Invalid tag: " + this.value); } else { return UriEncoder.decode(this.value.substring("tag:yaml.org,2002:".length())); } }
UriEncoder.decode方法:
public static String decode(String buff) { try { return URLDecoder.decode(buff, "UTF-8"); } catch (UnsupportedEncodingException var2) { throw new YAMLException(var2); } }

122行 getClassForNode方法通过反射方式寻找反序列化的类:

然后通过反射方式设置值:

