2021-11-15 eureka xstream deserialization RCE recurrence analysis

Vulnerability cause

Modify the value of eureka.client.serviceUrl.defaultZone to an external malicious url by post submission to /env, and then after submitting via /refresh post, parse the xstream deserialization in the url to get the command execution code

Debugging process

Examination questions and personal skills

Let’s first understand the principle. It can be seen that the external malicious payload will be obtained and parsed through /refresh. In other words, the code block that caused the vulnerability should be in the request and response parts.
Let’s start with DispatchServlet and find out step by step where calc will be called. The debugging breakpoints are as shown in the figure:

Insert image description here
During the debugging process, the calculator will automatically pop up, which is caused by the following error:
Insert image description here
I audited the error in the end, but I don’t know if it was because of a timeout that caused the calculator to be reacquired and then an error was reported (I will learn more about it later) It has something to do with the hot update mechanism of spring cloud, which can be bypassed by modifying the heartbeat and service renewal in the configuration file). This makes it more painful for me to find the code block causing the vulnerability, and I often fall into misunderstandings. I thought about changing the command, but how to distinguish other commands is a problem. Later, I found that in the payload server built by flask, if the pop-up window caused by timeout can be roughly judged: the proxy review process is boring, but the time passes quickly
Insert image description here
. Sometimes debugging is too fast and I can’t find the previous code, so I try to use screen recording to find it to avoid debugging it again.

debug process

According to the cause of the vulnerability, it can be understood that the final result is due to /refresh. This is similar to spring cloud SnakeYAML RCE. The call to /refreshh is processed through the RefreshEndpoint class, and then the ContextRefresh class is called to obtain and update the configuration, and at the same time publish the EnvironmentChangeEnvent event. The final refreshAll method is to clear all beans within the scope of the target cache, that is, to eliminate the previous objects and regenerate new objects.

Insert image description here
Then in the publish event method in refreshAll, the listener and bean are registered.
Insert image description here
After the registration is completed, the client registration begins, and eureka.client.serviceUrl.defaultZone starts to be registered here. Call the fetchRegistry method in the constructor method of DiscoveryClient to obtain registration information:
Insert image description here
The getAndStoreFullRegistry() method obtains all registration information from Eureka Server and then saves it locally. In this process, the URL will be requested and the data returned Generate it as an application instance, but an error will be reported in this step.
The final call is the AbstractJerseyEurekaHttpClient.getApplicationsInternal method

private EurekaHttpResponse<Applications> getApplicationsInternal(String urlPath, String[] regions) {
    
    
        ClientResponse response = null;
        String regionsParamValue = null;
        try {
    
    
            WebResource webResource = jerseyClient.resource(serviceUrl).path(urlPath);
            if (regions != null && regions.length > 0) {
    
    
                regionsParamValue = StringUtil.join(regions);
                webResource = webResource.queryParam("regions", regionsParamValue);
            }
            Builder requestBuilder = webResource.getRequestBuilder();
            addExtraHeaders(requestBuilder);
            response = requestBuilder.accept(MediaType.APPLICATION_JSON_TYPE).get(ClientResponse.class);

            Applications applications = null;
            if (response.getStatus() == Status.OK.getStatusCode() && response.hasEntity()) {
    
    
            //这一步报错Cannot execute request on any known server
                applications = response.getEntity(Applications.class);
            }
            return anEurekaHttpResponse(response.getStatus(), Applications.class)
                    .headers(headersOf(response))
                    .entity(applications)
                    .build();
        } finally {
    
    
            if (logger.isDebugEnabled()) {
    
    
                logger.debug("Jersey HTTP GET {}/{}?{}; statusCode={}",
                        serviceUrl, urlPath,
                        regionsParamValue == null ? "" : "regions=" + regionsParamValue,
                        response == null ? "N/A" : response.getStatus()
                );
            }
            if (response != null) {
    
    
                response.close();
            }
        }
    }

After that, the xstream serialized content is obtained, and then deserialized to implement command injection.
Subsequent calls are as follows:
Insert image description here

Repair suggestions

The root cause is that /env can be opened without authorization and can accept post data, so unauthorized access to /env is disabled:
add the following code to the configuration

endpoints.env.enabled= false

Add security and open
pom.xml

<dependency>
  <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

In the configuration file:

management.security.enabled=true
security.user.name=admin
security.user.password=admin!@#123

References

In-depth understanding of how Spring Cloud implements hot updates.
Spring Cloud configuration refresh
SimpleApplicationEventMulticaster parsing
Eureka (3) - client registration process

Guess you like

Origin blog.csdn.net/qq_40519543/article/details/121366695