SnakeYaml反序列化漏洞分析

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方法:

loadFromReader方法传入了两个参数,streamReader和type,并调用了getSingleData方法:

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方法:

constructDocument调用constructObject

继续跟进: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方法通过反射方式寻找反序列化的类:

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

发表评论