diff --git a/infection_monkey/system_info/__init__.py b/infection_monkey/system_info/__init__.py index 0d2828d88..52848673a 100644 --- a/infection_monkey/system_info/__init__.py +++ b/infection_monkey/system_info/__init__.py @@ -126,5 +126,7 @@ class InfoCollector(object): # 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]['Azure'] = True + if len(azure_creds) != 0: self.info["Azure"] = True diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index cbef9d973..2b9e2eccc 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -33,6 +33,7 @@ class ReportService: SAMBACRY = 3 SHELLSHOCK = 4 CONFICKER = 5 + AZURE = 6 class WARNINGS_DICT(Enum): CROSS_SEGMENT = 0 @@ -71,6 +72,19 @@ 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]) + + 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(): nodes = \ @@ -135,6 +149,26 @@ class ReportService: ) 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_type': 'system_info_collection', 'data.Azure': {'$exists': True}}, + {'data.credentials': 1, 'monkey_guid': 1} + ): + monkey_creds = telem['data']['credentials'] + if len(monkey_creds) == 0: + continue + origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname'] + new_creds = [{'username': user.replace(',', '.'), 'type': 'Clear Password', + 'origin': origin} for user in monkey_creds if 'Azure' in user] + creds.extend(new_creds) + return creds + @staticmethod def process_general_exploit(exploit): ip_addr = exploit['data']['machine']['ip_addr'] @@ -277,7 +311,7 @@ class ReportService: @staticmethod def get_issues(): - issues = ReportService.get_exploits() + ReportService.get_tunnels() + ReportService.get_cross_segment_issues() + issues = ReportService.get_exploits() + ReportService.get_tunnels() + ReportService.get_cross_segment_issues() + ReportService.get_azure_issues() issues_dict = {} for issue in issues: machine = issue['machine'] @@ -337,6 +371,8 @@ class ReportService: issues_byte_array[ReportService.ISSUES_DICT.SHELLSHOCK.value] = True elif issue['type'] == 'conficker': issues_byte_array[ReportService.ISSUES_DICT.CONFICKER.value] = True + elif issue['type'] == 'azure_password': + issues_byte_array[ReportService.ISSUES_DICT.AZURE.value] = True elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \ issue['username'] in config_users: issues_byte_array[ReportService.ISSUES_DICT.WEAK_PASSWORD.value] = True @@ -397,7 +433,8 @@ class ReportService: { 'scanned': ReportService.get_scanned(), 'exploited': ReportService.get_exploited(), - 'stolen_creds': ReportService.get_stolen_creds() + 'stolen_creds': ReportService.get_stolen_creds(), + 'azure_passwords': ReportService.get_azure_creds(), }, 'recommendations': { diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index 56c2c3881..2a13e46dd 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -21,7 +21,8 @@ class ReportPageComponent extends AuthComponent { ELASTIC: 2, SAMBACRY: 3, SHELLSHOCK: 4, - CONFICKER: 5 + CONFICKER: 5, + AZURE: 6 }; Warning = @@ -313,6 +314,11 @@ class ReportPageComponent extends AuthComponent { {this.state.report.overview.issues[this.Issue.WEAK_PASSWORD] ?
  • Machines are accessible using passwords supplied by the user during the Monkey’s configuration.
  • : null} + {this.state.report.overview.issues[this.Issue.AZURE] ? +
  • Machines contained plain text passwords accessible to attackers. For more info see Harvesting Azure Passwords.
  • : null} + : @@ -587,6 +593,21 @@ class ReportPageComponent extends AuthComponent { ); } + generateAzureIssue(issue) { + return ( +
  • + Azure VM Access configuration files should be deleted after use to avoid credential leakage. + + VM Access plugin configuration files were left on the machine. Credentials could be stolen from {issue.machine} for the following users{issue.users}. Read more about the security issue and remediation here. + +
  • + ); + } + generateConfickerIssue(issue) { return (
  • @@ -631,6 +652,8 @@ class ReportPageComponent extends AuthComponent { ); } + + generateIssue = (issue) => { let data; switch (issue.type) { @@ -670,6 +693,9 @@ class ReportPageComponent extends AuthComponent { case 'tunnel': data = this.generateTunnelIssue(issue); break; + case 'azure_password': + data = this.generateAzureIssue(issue); + break; } return data; };