diff --git a/docs/content/reference/exploiters/Zerologon.md b/docs/content/reference/exploiters/Zerologon.md index 4a893142b..76a524c03 100644 --- a/docs/content/reference/exploiters/Zerologon.md +++ b/docs/content/reference/exploiters/Zerologon.md @@ -7,12 +7,6 @@ tags: ["exploit", "windows"] The Zerologon exploiter exploits [CVE-2020-1472](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1472). -This exploiter is unsafe. -* It will temporarily change the target domain controller's password. -* It may break the target domain controller's communication with other systems in the network, affecting functionality. - -It is, therefore, **not** enabled by default. - ### Description @@ -20,6 +14,60 @@ An elevation of privilege vulnerability exists when an attacker establishes a vu To download the relevant security update and read more, click [here](https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2020-1472). +### A note on safety + +This exploiter is not safe for production or other sensitive environments. It +is, therefore, **not** enabled by default. + +During successful exploitation, the Zerologon exploiter: + +* will temporarily change the target domain controller's password. +* may break the target domain controller's communication with other systems in the network, affecting functionality. +* may change the administrator's password. +* will *attempt* to revert all changes. + +While the Zerologon exploiter is usually successful in reverting its changes +and restoring the original passwords, it sometimes fails. Restoring passwords +manually after the Zerologon exploiter has run is nontrivial. For information +on restoring the original passwords, see the section on manually restoring your +passwords. + +To minimize the risk posed by this exploiter, it is recommended that this +exploiter be run _only_ against VMs with a recent snapshot and _only_ in +testing or staging environments. + + +### Manually restoring your password + +This exploiter attempts to restore the original passwords after exploitation. +It is usually successful, but it sometimes fails. If this exploiter has changed +a password but was unable to restore the original, you can try the following +methods to restore the original password. + +#### Restore the VM from a recent snapshot + +If the affected system is a virtual machine, the simplest way to restore it to +a working state is to revert to a recent snapshot. + +#### Restore the administrator's password + +If you are unable to log in as the administrator, you can follow the +instructions +[here](https://www.top-password.com/knowledge/reset-windows-server-2019-password.html) +to regain access to the system. + +#### Use Reset-ComputerMachinePassword + +If you are able to login as the administrator, you can use the +[Reset-ComputerMachinePassword](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/reset-computermachinepassword?view=powershell-5.1) +powershell command to restore the domain controller's password. + + +#### Try a zerologon password restoration tool +If all other approaches fail, you can try the tools and steps found +[here](https://github.com/risksense/zerologon). + + ### Notes diff --git a/monkey/common/utils/exceptions.py b/monkey/common/utils/exceptions.py index 2c7121942..8396b423b 100644 --- a/monkey/common/utils/exceptions.py +++ b/monkey/common/utils/exceptions.py @@ -48,3 +48,7 @@ class VersionServerConnectionError(Exception): class FindingWithoutDetailsError(Exception): """ Raise when pulling events for a finding, but get none """ + + +class DomainControllerNameFetchError(FailedExploitationError): + """ Raise on failed attempt to extract domain controller's name """ diff --git a/monkey/infection_monkey/exploit/HostExploiter.py b/monkey/infection_monkey/exploit/HostExploiter.py index b3bdc7fd8..59d593b09 100644 --- a/monkey/infection_monkey/exploit/HostExploiter.py +++ b/monkey/infection_monkey/exploit/HostExploiter.py @@ -79,8 +79,8 @@ class HostExploiter(Plugin): result = None try: result = self._exploit_host() - except FailedExploitationError: - logger.debug('Exploiter failed.', exc_info=True) + except FailedExploitationError as e: + logger.debug(f'Exploiter failed: {e}.') except Exception: logger.error('Exception in exploit_host', exc_info=True) finally: diff --git a/monkey/infection_monkey/exploit/tests/zerologon_utils/test_vuln_assessment.py b/monkey/infection_monkey/exploit/tests/zerologon_utils/test_vuln_assessment.py index 7c833a67b..ca598ce7c 100644 --- a/monkey/infection_monkey/exploit/tests/zerologon_utils/test_vuln_assessment.py +++ b/monkey/infection_monkey/exploit/tests/zerologon_utils/test_vuln_assessment.py @@ -1,6 +1,7 @@ import pytest from nmb.NetBIOS import NetBIOS +from common.utils.exceptions import DomainControllerNameFetchError from infection_monkey.exploit.zerologon_utils.vuln_assessment import \ get_dc_details from infection_monkey.model.host import VictimHost @@ -38,8 +39,5 @@ def test_get_dc_details_no_netbios_names(host, monkeypatch): stub_queryIPForName = _get_stub_queryIPForName(NETBIOS_NAMES) monkeypatch.setattr(NetBIOS, "queryIPForName", stub_queryIPForName) - - dc_ip, dc_name, dc_handle = get_dc_details(host) - assert dc_ip == IP - assert dc_name == "" - assert dc_handle == "\\\\" + with pytest.raises(DomainControllerNameFetchError): + dc_ip, dc_name, dc_handle = get_dc_details(host) diff --git a/monkey/infection_monkey/exploit/zerologon.py b/monkey/infection_monkey/exploit/zerologon.py index dee5c349a..aa82d78c5 100644 --- a/monkey/infection_monkey/exploit/zerologon.py +++ b/monkey/infection_monkey/exploit/zerologon.py @@ -38,6 +38,7 @@ class ZerologonExploiter(HostExploiter): super().__init__(host) self.vulnerable_port = None self.exploit_info["credentials"] = {} + self.exploit_info["password_restored"] = None self._extracted_creds = {} def _exploit_host(self) -> bool: @@ -62,9 +63,11 @@ class ZerologonExploiter(HostExploiter): # Restore DC's original password. if _exploited: if self.restore_password(): + self.exploit_info["password_restored"] = True self.store_extracted_creds_for_exploitation() LOG.info("System exploited and password restored successfully.") else: + self.exploit_info["password_restored"] = False LOG.info("System exploited but couldn't restore password!") else: LOG.info("System was not exploited.") diff --git a/monkey/infection_monkey/exploit/zerologon_utils/vuln_assessment.py b/monkey/infection_monkey/exploit/zerologon_utils/vuln_assessment.py index 60df0db29..3470dd39a 100644 --- a/monkey/infection_monkey/exploit/zerologon_utils/vuln_assessment.py +++ b/monkey/infection_monkey/exploit/zerologon_utils/vuln_assessment.py @@ -4,6 +4,9 @@ from typing import Optional import nmb.NetBIOS from impacket.dcerpc.v5 import nrpc, rpcrt +from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT +from common.utils.exceptions import DomainControllerNameFetchError + LOG = logging.getLogger(__name__) @@ -18,14 +21,16 @@ def _get_dc_name(dc_ip: str) -> str: """ 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 "" - except BaseException as ex: - LOG.info(f"Exception: {ex}") + nb = nmb.NetBIOS.NetBIOS() + name = nb.queryIPForName( + ip=dc_ip, + timeout=MEDIUM_REQUEST_TIMEOUT + ) # returns either a list of NetBIOS names or None + + if name: + return name[0] + else: + raise DomainControllerNameFetchError("Couldn't get domain controller's name, maybe it's on external network?") def is_exploitable(zerologon_exploiter_object) -> (bool, Optional[rpcrt.DCERPC_v5]): diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py index ecc9b0029..88186e9ed 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py @@ -2,166 +2,142 @@ from monkey_island.cc.services.utils.typographic_symbols import WARNING_SIGN EXPLOITER_CLASSES = { "title": "Exploit class", - "description": "Click on exploiter to get more information about it." + WARNING_SIGN + - " Note that using unsafe exploits may cause crashes of the exploited machine/service.", + "description": "Click on exploiter to get more information about it." + + WARNING_SIGN + + " Note that using unsafe exploits may cause crashes of the exploited machine/service.", "type": "string", "anyOf": [ { "type": "string", - "enum": [ - "SmbExploiter" - ], + "enum": ["SmbExploiter"], "title": "SMB Exploiter", "safe": True, "attack_techniques": ["T1110", "T1075", "T1035"], "info": "Brute forces using credentials provided by user and" - " hashes gathered by mimikatz.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/smbexec/" + " hashes gathered by mimikatz.", + "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/smbexec/", }, { "type": "string", - "enum": [ - "WmiExploiter" - ], + "enum": ["WmiExploiter"], "title": "WMI Exploiter", "safe": True, "attack_techniques": ["T1110", "T1106"], "info": "Brute forces WMI (Windows Management Instrumentation) " - "using credentials provided by user and hashes gathered by mimikatz.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/wmiexec/" + "using credentials provided by user and hashes gathered by mimikatz.", + "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/wmiexec/", }, { "type": "string", - "enum": [ - "MSSQLExploiter" - ], + "enum": ["MSSQLExploiter"], "title": "MSSQL Exploiter", "safe": True, "attack_techniques": ["T1110"], "info": "Tries to brute force into MsSQL server and uses insecure " - "configuration to execute commands on server.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/mssql/" + "configuration to execute commands on server.", + "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/mssql/", }, { "type": "string", - "enum": [ - "Ms08_067_Exploiter" - ], + "enum": ["Ms08_067_Exploiter"], "title": "MS08-067 Exploiter", "safe": False, "info": "Unsafe exploiter, that might cause system crash due to the use of buffer overflow. " - "Uses MS08-067 vulnerability.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/ms08-067/" + "Uses MS08-067 vulnerability.", + "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/ms08-067/", }, { "type": "string", - "enum": [ - "SSHExploiter" - ], + "enum": ["SSHExploiter"], "title": "SSH Exploiter", "safe": True, "attack_techniques": ["T1110", "T1145", "T1106"], "info": "Brute forces using credentials provided by user and SSH keys gathered from systems.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/sshexec/" + "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/sshexec/", }, { "type": "string", - "enum": [ - "ShellShockExploiter" - ], + "enum": ["ShellShockExploiter"], "title": "ShellShock Exploiter", "safe": True, "info": "CVE-2014-6271, based on logic from " - "https://github.com/nccgroup/shocker/blob/master/shocker.py .", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/shellshock/" + "https://github.com/nccgroup/shocker/blob/master/shocker.py .", + "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/shellshock/", }, { "type": "string", - "enum": [ - "SambaCryExploiter" - ], + "enum": ["SambaCryExploiter"], "title": "SambaCry Exploiter", "safe": True, "info": "Bruteforces and searches for anonymous shares. Uses Impacket.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/sambacry/" + "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/sambacry/", }, { "type": "string", - "enum": [ - "ElasticGroovyExploiter" - ], + "enum": ["ElasticGroovyExploiter"], "title": "ElasticGroovy Exploiter", "safe": True, "info": "CVE-2015-1427. Logic is based on Metasploit module.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/elasticgroovy/" + "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/elasticgroovy/", }, { "type": "string", - "enum": [ - "Struts2Exploiter" - ], + "enum": ["Struts2Exploiter"], "title": "Struts2 Exploiter", "safe": True, "info": "Exploits struts2 java web framework. CVE-2017-5638. Logic based on " - "https://www.exploit-db.com/exploits/41570 .", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/struts2/" + "https://www.exploit-db.com/exploits/41570 .", + "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/struts2/", }, { "type": "string", - "enum": [ - "WebLogicExploiter" - ], + "enum": ["WebLogicExploiter"], "title": "WebLogic Exploiter", "safe": True, "info": "Exploits CVE-2017-10271 and CVE-2019-2725 vulnerabilities on WebLogic server.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/weblogic/" + "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/weblogic/", }, { "type": "string", - "enum": [ - "HadoopExploiter" - ], + "enum": ["HadoopExploiter"], "title": "Hadoop/Yarn Exploiter", "safe": True, "info": "Remote code execution on HADOOP server with YARN and default settings. " - "Logic based on https://github.com/vulhub/vulhub/tree/master/hadoop/unauthorized-yarn.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/hadoop/" + "Logic based on https://github.com/vulhub/vulhub/tree/master/hadoop/unauthorized-yarn.", + "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/hadoop/", }, { "type": "string", - "enum": [ - "VSFTPDExploiter" - ], + "enum": ["VSFTPDExploiter"], "title": "VSFTPD Exploiter", "safe": True, "info": "Exploits a malicious backdoor that was added to the VSFTPD download archive. " - "Logic based on Metasploit module.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/vsftpd/" + "Logic based on Metasploit module.", + "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/vsftpd/", }, { "type": "string", - "enum": [ - "DrupalExploiter" - ], + "enum": ["DrupalExploiter"], "title": "Drupal Exploiter", "safe": True, "info": "Exploits a remote command execution vulnerability in a Drupal server," - "for which certain modules (such as RESTful Web Services) are enabled.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/drupal/" + "for which certain modules (such as RESTful Web Services) are enabled.", + "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/drupal/", }, { "type": "string", - "enum": [ - "ZerologonExploiter" - ], + "enum": ["ZerologonExploiter"], "title": "Zerologon Exploiter", "safe": False, "info": "Exploits a privilege escalation vulnerability (CVE-2020-1472) in a Windows " - "server domain controller by using the Netlogon Remote Protocol (MS-NRPC). " - "This exploiter changes the password of a Windows server domain controller " - "account and could prevent the victim domain controller from communicating " - "with other domain controllers.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/zerologon/" - } - ] + "server domain controller by using the Netlogon Remote Protocol (MS-NRPC). " + "This exploiter changes the password of a Windows server domain controller " + "account and then attempts to restore it. The victim domain controller " + "will be unable to communicate with other domain controllers until the original " + "password has been restored. If Infection Monkey fails to restore the " + "password automatically, you'll have to do it manually. For more " + "information, see the documentation.", + "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/zerologon/", + }, + ], } diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index d48a753ed..8b57eaec2 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -65,6 +65,7 @@ class ReportService: VSFTPD = 13 DRUPAL = 14 ZEROLOGON = 15 + ZEROLOGON_PASSWORD_RESTORE_FAILED = 16 class WARNINGS_DICT(Enum): CROSS_SEGMENT = 0 @@ -394,6 +395,7 @@ class ReportService: def process_zerologon_exploit(exploit): processed_exploit = ReportService.process_general_exploit(exploit) processed_exploit['type'] = 'zerologon' + processed_exploit['password_restored'] = exploit['data']['info']['password_restored'] return processed_exploit @staticmethod @@ -713,6 +715,8 @@ class ReportService: elif issue['type'] == 'drupal': issues_byte_array[ReportService.ISSUES_DICT.DRUPAL.value] = True elif issue['type'] == 'zerologon': + if not issue['password_restored']: + issues_byte_array[ReportService.ISSUES_DICT.ZEROLOGON_PASSWORD_RESTORE_FAILED.value] = True issues_byte_array[ReportService.ISSUES_DICT.ZEROLOGON.value] = True elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \ issue['username'] in config_users or issue['type'] == 'ssh': diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js index 1d6072ece..63ff12444 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js @@ -13,9 +13,11 @@ import ReportHeader, {ReportTypes} from './common/ReportHeader'; import ReportLoader from './common/ReportLoader'; import SecurityIssuesGlance from './common/SecurityIssuesGlance'; import PrintReportButton from './common/PrintReportButton'; +import WarningIcon from '../ui-components/WarningIcon'; +import {Button} from 'react-bootstrap'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faMinus } from '@fortawesome/free-solid-svg-icons/faMinus'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import {faMinus} from '@fortawesome/free-solid-svg-icons/faMinus'; import guardicoreLogoImage from '../../images/guardicore-logo.png' import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons'; import '../../styles/App.css'; @@ -38,9 +40,14 @@ class ReportPageComponent extends AuthComponent { HADOOP: 10, PTH_CRIT_SERVICES_ACCESS: 11, MSSQL: 12, - VSFTPD: 13 + VSFTPD: 13, + DRUPAL: 14, + ZEROLOGON: 15, + ZEROLOGON_PASSWORD_RESTORE_FAILED: 16 }; + NotThreats = [this.Issue.ZEROLOGON_PASSWORD_RESTORE_FAILED]; + Warning = { CROSS_SEGMENT: 0, @@ -78,7 +85,7 @@ class ReportPageComponent extends AuthComponent { componentDidUpdate(prevProps) { if (this.props.report !== prevProps.report) { - this.setState({ report: this.props.report }) + this.setState({report: this.props.report}) } } @@ -105,7 +112,7 @@ class ReportPageComponent extends AuthComponent { print(); }}/> -
+
The first monkey run was started on {this.state.report.overview.monkey_start_time}. After {this.state.report.overview.monkey_duration}, all monkeys finished + className='badge badge-info'>{this.state.report.overview.monkey_start_time}. After {this.state.report.overview.monkey_duration}, all monkeys finished propagation attempts.
@@ -175,7 +182,7 @@ class ReportPageComponent extends AuthComponent { Usernames used for brute-forcing:
Passwords used for brute-forcing: @@ -233,7 +240,7 @@ class ReportPageComponent extends AuthComponent { generateReportFindingsSection() { return ( -
The Monkey discovered {this.state.report.glance.scanned.length} machines and + className='badge badge-warning'>{this.state.report.glance.scanned.length} machines and successfully breached {this.state.report.glance.exploited.length} of them. + className='badge badge-danger'>{this.state.report.glance.exploited.length} of them.
-From the attacker's point of view, the network looks like this:
-