99% done with RCR, not yet been tested.

This commit is contained in:
maor.rayzin 2018-10-25 14:17:31 +03:00
parent d02b9c2538
commit 17b344f62f
18 changed files with 375 additions and 430 deletions

View File

View File

@ -0,0 +1,83 @@
import wmi
import win32com
__author__ = 'maor.rayzin'
class MongoUtils:
def __init__(self):
# Static class
pass
@staticmethod
def fix_obj_for_mongo(o):
if type(o) == dict:
return dict([(k, MongoUtils.fix_obj_for_mongo(v)) for k, v in o.iteritems()])
elif type(o) in (list, tuple):
return [MongoUtils.fix_obj_for_mongo(i) for i in o]
elif type(o) in (int, float, bool):
return o
elif type(o) in (str, unicode):
# mongo dosn't like unprintable chars, so we use repr :/
return repr(o)
elif hasattr(o, "__class__") and o.__class__ == wmi._wmi_object:
return MongoUtils.fix_wmi_obj_for_mongo(o)
elif hasattr(o, "__class__") and o.__class__ == win32com.client.CDispatch:
try:
# objectSid property of ds_user is problematic and need thie special treatment.
# ISWbemObjectEx interface. Class Uint8Array ?
if str(o._oleobj_.GetTypeInfo().GetTypeAttr().iid) == "{269AD56A-8A67-4129-BC8C-0506DCFE9880}":
return o.Value
except:
pass
try:
return o.GetObjectText_()
except:
pass
return repr(o)
else:
return repr(o)
@staticmethod
def fix_wmi_obj_for_mongo(o):
row = {}
for prop in o.properties:
try:
value = getattr(o, prop)
except wmi.x_wmi:
# This happens in Win32_GroupUser when the user is a domain user.
# For some reason, the wmi query for PartComponent fails. This table
# is actually contains references to Win32_UserAccount and Win32_Group.
# so instead of reading the content to the Win32_UserAccount, we store
# only the id of the row in that table, and get all the other information
# from that table while analyzing the data.
value = o.properties[prop].value
row[prop] = MongoUtils.fix_obj_for_mongo(value)
for method_name in o.methods:
if not method_name.startswith("GetOwner"):
continue
method = getattr(o, method_name)
try:
value = method()
value = MongoUtils.fix_obj_for_mongo(value)
row[method_name[3:]] = value
except wmi.x_wmi:
continue
return row

View File

@ -0,0 +1,25 @@
import _winreg
from common.utils.mongo_utils import MongoUtils
__author__ = 'maor.rayzin'
class RegUtils:
def __init__(self):
# Static class
pass
@staticmethod
def get_reg_key(subkey_path, store=_winreg.HKEY_LOCAL_MACHINE):
key = _winreg.ConnectRegistry(None, store)
subkey = _winreg.OpenKey(key, subkey_path)
d = dict([_winreg.EnumValue(subkey, i)[:2] for i in xrange(_winreg.QueryInfoKey(subkey)[0])])
d = MongoUtils.fix_obj_for_mongo(d)
subkey.Close()
key.Close()
return d

View File

@ -0,0 +1,27 @@
import wmi
from mongo_utils import MongoUtils
__author__ = 'maor.rayzin'
class WMIUtils:
def __init__(self):
# Static class
pass
@staticmethod
def get_wmi_class(class_name, moniker="//./root/cimv2", properties=None):
_wmi = wmi.WMI(moniker=moniker)
try:
if not properties:
wmi_class = getattr(_wmi, class_name)()
else:
wmi_class = getattr(_wmi, class_name)(properties)
except wmi.x_wmi:
return
return MongoUtils.fix_obj_for_mongo(wmi_class)

View File

@ -57,6 +57,7 @@ class MimikatzCollector(object):
Gets the logon info from mimikatz. Gets the logon info from mimikatz.
Returns a dictionary of users with their known credentials. Returns a dictionary of users with their known credentials.
""" """
LOG.info('Getting mimikatz logon information')
if not self._isInit: if not self._isInit:
return {} return {}
LOG.debug("Running mimikatz collector") LOG.debug("Running mimikatz collector")

View File

@ -1,125 +1,20 @@
import os import os
import logging import logging
import sys import sys
sys.coinit_flags = 0 # needed for proper destruction of the wmi python module
import wmi
import win32com
import _winreg
from mimikatz_collector import MimikatzCollector sys.coinit_flags = 0 # needed for proper destruction of the wmi python module
from . import InfoCollector
import infection_monkey.config import infection_monkey.config
from infection_monkey.system_info.mimikatz_collector import MimikatzCollector from infection_monkey.system_info.mimikatz_collector import MimikatzCollector
from infection_monkey.system_info import InfoCollector from infection_monkey.system_info import InfoCollector
from infection_monkey.system_info.wmi_consts import WMI_CLASSES
from common.utils.wmi_utils import WMIUtils
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
LOG.info('started windows info collector') LOG.info('started windows info collector')
__author__ = 'uri' __author__ = 'uri'
WMI_CLASSES = {"Win32_OperatingSystem", "Win32_ComputerSystem", "Win32_LoggedOnUser", "Win32_UserAccount",
"Win32_UserProfile", "Win32_Group", "Win32_GroupUser", "Win32_Product", "Win32_Service",
"Win32_OptionalFeature"}
# These wmi queries are able to return data about all the users & machines in the domain.
# For these queries to work, the monkey shohuld be run on a domain machine and
#
# monkey should run as *** SYSTEM *** !!!
#
WMI_LDAP_CLASSES = {"ds_user": ("DS_sAMAccountName", "DS_userPrincipalName",
"DS_sAMAccountType", "ADSIPath", "DS_userAccountControl",
"DS_objectSid", "DS_objectClass", "DS_memberOf",
"DS_primaryGroupID", "DS_pwdLastSet", "DS_badPasswordTime",
"DS_badPwdCount", "DS_lastLogon", "DS_lastLogonTimestamp",
"DS_lastLogoff", "DS_logonCount", "DS_accountExpires"),
"ds_group": ("DS_whenChanged", "DS_whenCreated", "DS_sAMAccountName",
"DS_sAMAccountType", "DS_objectSid", "DS_objectClass",
"DS_name", "DS_memberOf", "DS_member", "DS_instanceType",
"DS_cn", "DS_description", "DS_distinguishedName", "ADSIPath"),
"ds_computer": ("DS_dNSHostName", "ADSIPath", "DS_accountExpires",
"DS_adminDisplayName", "DS_badPasswordTime",
"DS_badPwdCount", "DS_cn", "DS_distinguishedName",
"DS_instanceType", "DS_lastLogoff", "DS_lastLogon",
"DS_lastLogonTimestamp", "DS_logonCount", "DS_objectClass",
"DS_objectSid", "DS_operatingSystem", "DS_operatingSystemVersion",
"DS_primaryGroupID", "DS_pwdLastSet", "DS_sAMAccountName",
"DS_sAMAccountType", "DS_servicePrincipalName", "DS_userAccountControl",
"DS_whenChanged", "DS_whenCreated"),
}
def fix_obj_for_mongo(o):
if type(o) == dict:
return dict([(k, fix_obj_for_mongo(v)) for k, v in o.iteritems()])
elif type(o) in (list, tuple):
return [fix_obj_for_mongo(i) for i in o]
elif type(o) in (int, float, bool):
return o
elif type(o) in (str, unicode):
# mongo dosn't like unprintable chars, so we use repr :/
return repr(o)
elif hasattr(o, "__class__") and o.__class__ == wmi._wmi_object:
return fix_wmi_obj_for_mongo(o)
elif hasattr(o, "__class__") and o.__class__ == win32com.client.CDispatch:
try:
# objectSid property of ds_user is problematic and need thie special treatment.
# ISWbemObjectEx interface. Class Uint8Array ?
if str(o._oleobj_.GetTypeInfo().GetTypeAttr().iid) == "{269AD56A-8A67-4129-BC8C-0506DCFE9880}":
return o.Value
except:
pass
try:
return o.GetObjectText_()
except:
pass
return repr(o)
else:
return repr(o)
def fix_wmi_obj_for_mongo(o):
row = {}
for prop in o.properties:
try:
value = getattr(o, prop)
except wmi.x_wmi:
# This happens in Win32_GroupUser when the user is a domain user.
# For some reason, the wmi query for PartComponent fails. This table
# is actually contains references to Win32_UserAccount and Win32_Group.
# so instead of reading the content to the Win32_UserAccount, we store
# only the id of the row in that table, and get all the other information
# from that table while analyzing the data.
value = o.properties[prop].value
row[prop] = fix_obj_for_mongo(value)
for method_name in o.methods:
if not method_name.startswith("GetOwner"):
continue
method = getattr(o, method_name)
try:
value = method()
value = fix_obj_for_mongo(value)
row[method_name[3:]] = value
except wmi.x_wmi:
continue
return row
class WindowsInfoCollector(InfoCollector): class WindowsInfoCollector(InfoCollector):
""" """
@ -147,8 +42,8 @@ class WindowsInfoCollector(InfoCollector):
self.get_wmi_info() self.get_wmi_info()
LOG.debug('finished get_wmi_info') LOG.debug('finished get_wmi_info')
#self.get_reg_key(r"SYSTEM\CurrentControlSet\Control\Lsa")
self.get_installed_packages() self.get_installed_packages()
LOG.debug('Got installed packages')
mimikatz_collector = MimikatzCollector() mimikatz_collector = MimikatzCollector()
mimikatz_info = mimikatz_collector.get_logon_info() mimikatz_info = mimikatz_collector.get_logon_info()
@ -156,39 +51,17 @@ class WindowsInfoCollector(InfoCollector):
if "credentials" in self.info: if "credentials" in self.info:
self.info["credentials"].update(mimikatz_info) self.info["credentials"].update(mimikatz_info)
self.info["mimikatz"] = mimikatz_collector.get_mimikatz_text() self.info["mimikatz"] = mimikatz_collector.get_mimikatz_text()
else:
LOG.info('No mimikatz info was gathered')
return self.info return self.info
def get_installed_packages(self): def get_installed_packages(self):
LOG.info('getting installed packages')
self.info["installed_packages"] = os.popen("dism /online /get-packages").read() self.info["installed_packages"] = os.popen("dism /online /get-packages").read()
self.info["installed_features"] = os.popen("dism /online /get-features").read() self.info["installed_features"] = os.popen("dism /online /get-features").read()
def get_wmi_info(self): def get_wmi_info(self):
LOG.info('getting wmi info')
for wmi_class_name in WMI_CLASSES: for wmi_class_name in WMI_CLASSES:
self.info['wmi'][wmi_class_name] = self.get_wmi_class(wmi_class_name) self.info['wmi'][wmi_class_name] = WMIUtils.get_wmi_class(wmi_class_name)
def get_wmi_class(self, class_name, moniker="//./root/cimv2", properties=None):
_wmi = wmi.WMI(moniker=moniker)
try:
if not properties:
wmi_class = getattr(_wmi, class_name)()
else:
wmi_class = getattr(_wmi, class_name)(properties)
except wmi.x_wmi:
return
return fix_obj_for_mongo(wmi_class)
def get_reg_key(self, subkey_path, store=_winreg.HKEY_LOCAL_MACHINE):
key = _winreg.ConnectRegistry(None, store)
subkey = _winreg.OpenKey(key, subkey_path)
d = dict([_winreg.EnumValue(subkey, i)[:2] for i in xrange(_winreg.QueryInfoKey(subkey)[0])])
d = fix_obj_for_mongo(d)
self.info['reg'][subkey_path] = d
subkey.Close()
key.Close()

View File

@ -0,0 +1,32 @@
WMI_CLASSES = {"Win32_OperatingSystem", "Win32_ComputerSystem", "Win32_LoggedOnUser", "Win32_UserAccount",
"Win32_UserProfile", "Win32_Group", "Win32_GroupUser", "Win32_Product", "Win32_Service",
"Win32_OptionalFeature"}
# These wmi queries are able to return data about all the users & machines in the domain.
# For these queries to work, the monkey should be run on a domain machine and
#
# monkey should run as *** SYSTEM *** !!!
#
WMI_LDAP_CLASSES = {"ds_user": ("DS_sAMAccountName", "DS_userPrincipalName",
"DS_sAMAccountType", "ADSIPath", "DS_userAccountControl",
"DS_objectSid", "DS_objectClass", "DS_memberOf",
"DS_primaryGroupID", "DS_pwdLastSet", "DS_badPasswordTime",
"DS_badPwdCount", "DS_lastLogon", "DS_lastLogonTimestamp",
"DS_lastLogoff", "DS_logonCount", "DS_accountExpires"),
"ds_group": ("DS_whenChanged", "DS_whenCreated", "DS_sAMAccountName",
"DS_sAMAccountType", "DS_objectSid", "DS_objectClass",
"DS_name", "DS_memberOf", "DS_member", "DS_instanceType",
"DS_cn", "DS_description", "DS_distinguishedName", "ADSIPath"),
"ds_computer": ("DS_dNSHostName", "ADSIPath", "DS_accountExpires",
"DS_adminDisplayName", "DS_badPasswordTime",
"DS_badPwdCount", "DS_cn", "DS_distinguishedName",
"DS_instanceType", "DS_lastLogoff", "DS_lastLogon",
"DS_lastLogonTimestamp", "DS_logonCount", "DS_objectClass",
"DS_objectSid", "DS_operatingSystem", "DS_operatingSystemVersion",
"DS_primaryGroupID", "DS_pwdLastSet", "DS_sAMAccountName",
"DS_sAMAccountType", "DS_servicePrincipalName", "DS_userAccountControl",
"DS_whenChanged", "DS_whenCreated"),
}

View File

@ -17,7 +17,7 @@
"info_file_handler": { "info_file_handler": {
"class": "logging.handlers.RotatingFileHandler", "class": "logging.handlers.RotatingFileHandler",
"level": "DEBUG", "level": "INFO",
"formatter": "simple", "formatter": "simple",
"filename": "info.log", "filename": "info.log",
"maxBytes": 10485760, "maxBytes": 10485760,

View File

@ -186,19 +186,11 @@ class Telemetry(flask_restful.Resource):
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)
if 'mimikatz' in telemetry_json['data']: if 'mimikatz' in telemetry_json['data']:
users_secrets = user_info.extract_secrets_from_mimikatz(telemetry_json['data'].get('mimikatz', '')) users_secrets = user_info.MimikatzSecrets.\
extract_secrets_from_mimikatz(telemetry_json['data'].get('mimikatz', ''))
if 'wmi' in telemetry_json['data']: if 'wmi' in telemetry_json['data']:
wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets) wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets)
wmi_handler.add_groups_to_collection() wmi_handler.process_and_handle_wmi_info()
wmi_handler.add_users_to_collection()
wmi_handler.create_group_user_connection()
wmi_handler.insert_info_to_mongo()
wmi_handler.add_admin(wmi_handler.info_for_mongo[wmi_handler.ADMINISTRATORS_GROUP_KNOWN_SID], monkey_id)
wmi_handler.update_admins_retrospective()
wmi_handler.update_critical_services(telemetry_json['data']['wmi']['Win32_Service'],
telemetry_json['data']['wmi']['Win32_Product'],
monkey_id)
@staticmethod @staticmethod
def add_ip_to_ssh_keys(ip, ssh_info): def add_ip_to_ssh_keys(ip, ssh_info):

View File

@ -325,3 +325,7 @@ class NodeService:
@staticmethod @staticmethod
def get_node_hostname(node): def get_node_hostname(node):
return node['hostname'] if 'hostname' in node else node['os']['version'] return node['hostname'] if 'hostname' in node else node['os']['version']
@staticmethod
def get_hostname_by_id(node_id):
NodeService.get_node_hostname(mongo.db.monkey.find_one({'_id': node_id}, {'hostname': 1}))

View File

@ -1,16 +1,16 @@
import uuid from itertools import product
from itertools import combinations, product
from cc.database import mongo from cc.database import mongo
from bson import ObjectId from bson import ObjectId
from cc.services.node import NodeService
__author__ = 'maor.rayzin'
class PTHReportService(object): class PTHReportService(object):
@staticmethod @staticmethod
def get_duplicated_passwords_nodes(): def __dup_passwords_mongoquery():
users_cred_groups = []
pipeline = [ pipeline = [
{"$match": { {"$match": {
'NTLM_secret': { 'NTLM_secret': {
@ -26,74 +26,16 @@ class PTHReportService(object):
}}, }},
{'$match': {'count': {'$gt': 1}}} {'$match': {'count': {'$gt': 1}}}
] ]
docs = mongo.db.groupsandusers.aggregate(pipeline) return mongo.db.groupsandusers.aggregate(pipeline)
for doc in docs:
users_list = []
for user in doc['Docs']:
hostname = None
if user['machine_id']:
machine = mongo.db.monkey.find_one({'_id': ObjectId(user['machine_id'])}, {'hostname': 1})
if machine.get('hostname'):
hostname = machine['hostname']
users_list.append({'username': user['name'], 'domain_name': user['domain_name'],
'hostname': hostname})
users_cred_groups.append({'cred_groups': users_list})
return users_cred_groups
@staticmethod @staticmethod
def get_duplicated_passwords_issues(): def __get_admin_on_machines_format(admin_on_machines):
user_groups = PTHReportService.get_duplicated_passwords_nodes()
issues = [] machines = mongo.db.monkey.find({'_id': {'$in': admin_on_machines}}, {'hostname': 1})
users_gathered = [] return [i['hostname'] for i in list(machines)]
for group in user_groups:
for user_info in group['cred_groups']:
users_gathered.append(user_info['username'])
issues.append(
{
'type': 'shared_passwords_domain' if user_info['domain_name'] else 'shared_passwords',
'machine': user_info['hostname'] if user_info['hostname'] else user_info['domain_name'],
'shared_with': [i['username'] for i in group['cred_groups']],
'is_local': False if user_info['domain_name'] else True
}
)
break
return issues
@staticmethod @staticmethod
def get_shared_admins_nodes(): def __strong_users_on_crit_query():
admins = mongo.db.groupsandusers.find({'type': 1, 'admin_on_machines.1': {'$exists': True}},
{'admin_on_machines': 1, 'name': 1, 'domain_name': 1})
admins_info_list = []
for admin in admins:
machines = mongo.db.monkey.find({'_id': {'$in': admin['admin_on_machines']}}, {'hostname': 1})
# appends the host names of the machines this user is admin on.
admins_info_list.append({'name': admin['name'],'domain_name': admin['domain_name'],
'admin_on_machines': [i['hostname'] for i in list(machines)]})
return admins_info_list
@staticmethod
def get_shared_admins_issues():
admins_info = PTHReportService.get_shared_admins_nodes()
issues = []
for admin in admins_info:
issues.append(
{
'is_local': False,
'type': 'shared_admins_domain',
'machine': admin['domain_name'],
'username': admin['name'],
'shared_machines': admin['admin_on_machines'],
}
)
return issues
@staticmethod
def get_strong_users_on_critical_machines_nodes():
crit_machines = {}
pipeline = [ pipeline = [
{ {
'$unwind': '$admin_on_machines' '$unwind': '$admin_on_machines'
@ -117,13 +59,82 @@ class PTHReportService(object):
'$unwind': '$critical_machine' '$unwind': '$critical_machine'
} }
] ]
docs = mongo.db.groupsandusers.aggregate(pipeline) return mongo.db.groupsandusers.aggregate(pipeline)
@staticmethod
def get_duplicated_passwords_nodes():
users_cred_groups = []
docs = PTHReportService.__dup_passwords_mongoquery()
for doc in docs:
users_list = [
{
'username': user['name'],
'domain_name': user['domain_name'],
'hostname': NodeService.get_hostname_by_id(ObjectId(user['machine_id']))
if user['machine_id'] else None
} for user in doc['Docs']
]
users_cred_groups.append({'cred_groups': users_list})
return users_cred_groups
@staticmethod
def get_duplicated_passwords_issues():
user_groups = PTHReportService.get_duplicated_passwords_nodes()
issues = []
for group in user_groups:
user_info = group['cred_groups'][0]
issues.append(
{
'type': 'shared_passwords_domain' if user_info['domain_name'] else 'shared_passwords',
'machine': user_info['hostname'] if user_info['hostname'] else user_info['domain_name'],
'shared_with': [i['username'] for i in group['cred_groups']],
'is_local': False if user_info['domain_name'] else True
}
)
return issues
@staticmethod
def get_shared_admins_nodes():
# This mongo queries users the best solution to figure out if an array
# object has at least two objects in it, by making sure any value exists in the array index 1.
admins = mongo.db.groupsandusers.find({'type': 1, 'admin_on_machines.1': {'$exists': True}},
{'admin_on_machines': 1, 'name': 1, 'domain_name': 1})
return [
{
'name': admin['name'],
'domain_name': admin['domain_name'],
'admin_on_machines': PTHReportService.__get_admin_on_machines_format(admin['admin_on_machines'])
} for admin in admins
]
@staticmethod
def get_shared_admins_issues():
admins_info = PTHReportService.get_shared_admins_nodes()
return [
{
'is_local': False,
'type': 'shared_admins_domain',
'machine': admin['domain_name'],
'username': admin['name'],
'shared_machines': admin['admin_on_machines'],
}
for admin in admins_info]
@staticmethod
def get_strong_users_on_critical_machines_nodes():
crit_machines = {}
docs = PTHReportService.__strong_users_on_crit_query()
for doc in docs: for doc in docs:
hostname = str(doc['critical_machine']['hostname']) hostname = str(doc['critical_machine']['hostname'])
if not hostname in crit_machines: if hostname not in crit_machines:
crit_machines[hostname] = {} crit_machines[hostname] = {
crit_machines[hostname]['threatening_users'] = [] 'threatening_users': [],
crit_machines[hostname]['critical_services'] = doc['critical_machine']['critical_services'] 'critical_services': doc['critical_machine']['critical_services']
}
crit_machines[hostname]['threatening_users'].append( crit_machines[hostname]['threatening_users'].append(
{'name': str(doc['domain_name']) + '\\' + str(doc['name']), {'name': str(doc['domain_name']) + '\\' + str(doc['name']),
'creds_location': doc['secret_location']}) 'creds_location': doc['secret_location']})
@ -131,107 +142,92 @@ class PTHReportService(object):
@staticmethod @staticmethod
def get_strong_users_on_crit_issues(): def get_strong_users_on_crit_issues():
issues = []
crit_machines = PTHReportService.get_strong_users_on_critical_machines_nodes() crit_machines = PTHReportService.get_strong_users_on_critical_machines_nodes()
for machine in crit_machines:
issues.append(
{
'type': 'strong_users_on_crit',
'machine': machine,
'services': crit_machines[machine].get('critical_services'),
'threatening_users': [i['name'] for i in crit_machines[machine]['threatening_users']]
}
)
return issues return [
{
'type': 'strong_users_on_crit',
'machine': machine,
'services': crit_machines[machine].get('critical_services'),
'threatening_users': [i['name'] for i in crit_machines[machine]['threatening_users']]
} for machine in crit_machines
]
@staticmethod @staticmethod
def get_strong_users_on_crit_details(): def get_strong_users_on_crit_details():
table_entries = []
user_details = {} user_details = {}
crit_machines = PTHReportService.get_strong_users_on_critical_machines_nodes() crit_machines = PTHReportService.get_strong_users_on_critical_machines_nodes()
for machine in crit_machines: for machine in crit_machines:
for user in crit_machines[machine]['threatening_users']: for user in crit_machines[machine]['threatening_users']:
username = user['name'] username = user['name']
if username not in user_details: if username not in user_details:
user_details[username] = {} user_details[username] = {
user_details[username]['machines'] = [] 'machines': [],
user_details[username]['services'] = [] 'services': []
}
user_details[username]['machines'].append(machine) user_details[username]['machines'].append(machine)
user_details[username]['services'] += crit_machines[machine]['critical_services'] user_details[username]['services'] += crit_machines[machine]['critical_services']
for user in user_details: return [
table_entries.append( {
{ 'username': user,
'username': user, 'machines': user_details[user]['machines'],
'machines': user_details[user]['machines'], 'services_names': user_details[user]['services']
'services_names': user_details[user]['services'] } for user in user_details
} ]
)
return table_entries
@staticmethod @staticmethod
def generate_map_nodes(): def generate_map_nodes():
nodes_list = []
monkeys = mongo.db.monkey.find({}, {'_id': 1, 'hostname': 1, 'critical_services': 1, 'ip_addresses': 1}) monkeys = mongo.db.monkey.find({}, {'_id': 1, 'hostname': 1, 'critical_services': 1, 'ip_addresses': 1})
for monkey in monkeys:
critical_services = monkey.get('critical_services', []) return [
nodes_list.append({ {
'id': monkey['_id'], 'id': monkey['_id'],
'label': '{0} : {1}'.format(monkey['hostname'], monkey['ip_addresses'][0]), 'label': '{0} : {1}'.format(monkey['hostname'], monkey['ip_addresses'][0]),
'group': 'critical' if critical_services else 'normal', 'group': 'critical' if monkey.get('critical_services', []) else 'normal',
'services': critical_services, 'services': monkey.get('critical_services', []),
'hostname': monkey['hostname'] 'hostname': monkey['hostname']
}) } for monkey in monkeys
]
return nodes_list
@staticmethod @staticmethod
def generate_edge_nodes(): def generate_edges():
edges_list = [] edges_list = []
pipeline = [
comp_users = mongo.db.groupsandusers.find(
{ {
'$match': {'admin_on_machines': {'$ne': []}, 'secret_location': {'$ne': []}, 'type': 1} 'admin_on_machines': {'$ne': []},
'secret_location': {'$ne': []},
'type': 1
}, },
{ {
'$project': {'admin_on_machines': 1, 'secret_location': 1} 'admin_on_machines': 1, 'secret_location': 1
} }
] )
comp_users = mongo.db.groupsandusers.aggregate(pipeline)
for user in comp_users: for user in comp_users:
pairs = PTHReportService.generate_edges_tuples(user['admin_on_machines'], user['secret_location']) # A list comp, to get all unique pairs of attackers and victims.
for pair in pairs: for pair in [pair for pair in product(user['admin_on_machines'], user['secret_location'])
if pair[0] != pair[1]]:
edges_list.append( edges_list.append(
{ {
'from': pair[1], 'from': pair[1],
'to': pair[0], 'to': pair[0],
'id': str(uuid.uuid4()) 'id': str(pair[1]) + str(pair[0])
} }
) )
return edges_list return edges_list
@staticmethod
def generate_edges_tuples(*lists):
for t in combinations(lists, 2):
for pair in product(*t):
# Don't output pairs containing duplicated elements
if pair[0] != pair[1]:
yield pair
@staticmethod @staticmethod
def get_pth_map(): def get_pth_map():
return { return {
'nodes': PTHReportService.generate_map_nodes(), 'nodes': PTHReportService.generate_map_nodes(),
'edges': PTHReportService.generate_edge_nodes() 'edges': PTHReportService.generate_edges()
} }
@staticmethod @staticmethod
def get_report(): def get_report():
pth_map = PTHReportService.get_pth_map()
PTHReportService.get_strong_users_on_critical_machines_nodes() PTHReportService.get_strong_users_on_critical_machines_nodes()
report = \ report = \
{ {
@ -242,8 +238,8 @@ class PTHReportService(object):
'pthmap': 'pthmap':
{ {
'nodes': PTHReportService.generate_map_nodes(), 'nodes': pth_map.get('nodes'),
'edges': PTHReportService.generate_edge_nodes() 'edges': pth_map.get('edges')
} }
} }

View File

@ -1,6 +1,5 @@
import itertools import itertools
import functools import functools
import pprint
import ipaddress import ipaddress
import logging import logging
@ -160,7 +159,7 @@ class ReportService:
@staticmethod @staticmethod
def get_stolen_creds(): def get_stolen_creds():
PASS_TYPE_DICT = {'password': 'Clear Password', 'lm_hash': 'LM hash', 'ntlm_hash': 'NTLM hash'} PASS_TYPE_DICT = {'password': 'Clear Password', 'lm_hash': 'LM hash', 'ntlm_hash': 'NTLM hash'}
creds = [] creds = set()
for telem in mongo.db.telemetry.find( for telem in mongo.db.telemetry.find(
{'telem_type': 'system_info_collection', 'data.credentials': {'$exists': True}}, {'telem_type': 'system_info_collection', 'data.credentials': {'$exists': True}},
{'data.credentials': 1, 'monkey_guid': 1} {'data.credentials': 1, 'monkey_guid': 1}
@ -177,14 +176,9 @@ class ReportService:
'type': PASS_TYPE_DICT[pass_type], 'type': PASS_TYPE_DICT[pass_type],
'origin': origin 'origin': origin
} }
if cred_row not in creds: creds.add(cred_row)
creds.append(cred_row)
logger.info('Stolen creds generated for reporting') logger.info('Stolen creds generated for reporting')
return creds return list(creds)
@staticmethod
def get_pth_shared_passwords():
pass
@staticmethod @staticmethod
def get_ssh_keys(): def get_ssh_keys():
@ -544,7 +538,7 @@ class ReportService:
domain_issues_dict = {} domain_issues_dict = {}
for issue in issues: for issue in issues:
if not issue.get('is_local', True): if not issue.get('is_local', True):
machine = issue.get('machine', '').upper() machine = issue.get('machine').upper()
if machine not in domain_issues_dict: if machine not in domain_issues_dict:
domain_issues_dict[machine] = [] domain_issues_dict[machine] = []
domain_issues_dict[machine].append(issue) domain_issues_dict[machine].append(issue)
@ -566,7 +560,7 @@ class ReportService:
issues_dict = {} issues_dict = {}
for issue in issues: for issue in issues:
if issue.get('is_local', True): if issue.get('is_local', True):
machine = issue.get('machine', '').upper() machine = issue.get('machine').upper()
if machine not in issues_dict: if machine not in issues_dict:
issues_dict[machine] = [] issues_dict[machine] = []
issues_dict[machine].append(issue) issues_dict[machine].append(issue)
@ -707,17 +701,14 @@ 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() 'ssh_keys': ReportService.get_ssh_keys(),
'strong_users': PTHReportService.get_strong_users_on_crit_details(),
'pth_map': PTHReportService.get_pth_map()
}, },
'recommendations': 'recommendations':
{ {
'issues': issues, 'issues': issues,
'domain_issues': domain_issues 'domain_issues': domain_issues
},
'pth':
{
'strong_users': PTHReportService.get_strong_users_on_crit_details(),
'map': PTHReportService.get_pth_map(),
} }
} }

View File

@ -1,45 +1,52 @@
__author__ = 'maor.rayzin'
def extract_sam_secrets(mim_string, users_dict): class MimikatzSecrets(object):
users_secrets = mim_string.split("\n42.")[1].split("\nSAMKey :")[1].split("\n\n")[1:]
if mim_string.count("\n42.") != 2: def __init__(self):
return {} # Static class
pass
for sam_user_txt in users_secrets: @staticmethod
sam_user = dict([map(unicode.strip, line.split(":")) for line in def extract_sam_secrets(mim_string, users_dict):
filter(lambda l: l.count(":") == 1, sam_user_txt.splitlines())]) users_secrets = mim_string.split("\n42.")[1].split("\nSAMKey :")[1].split("\n\n")[1:]
username = sam_user.get("User")
users_dict[username] = {}
ntlm = sam_user.get("NTLM") if mim_string.count("\n42.") != 2:
if "[hashed secret]" not in ntlm: return {}
continue
users_dict[username]['SAM'] = ntlm.replace("[hashed secret]", "").strip() for sam_user_txt in users_secrets:
sam_user = dict([map(unicode.strip, line.split(":")) for line in
filter(lambda l: l.count(":") == 1, sam_user_txt.splitlines())])
username = sam_user.get("User")
users_dict[username] = {}
ntlm = sam_user.get("NTLM")
if "[hashed secret]" not in ntlm:
continue
def extract_ntlm_secrets(mim_string, users_dict): users_dict[username]['SAM'] = ntlm.replace("[hashed secret]", "").strip()
if mim_string.count("\n42.") != 2: @staticmethod
return {} def extract_ntlm_secrets(mim_string, users_dict):
ntds_users = mim_string.split("\n42.")[2].split("\nRID :")[1:] if mim_string.count("\n42.") != 2:
return {}
for ntds_user_txt in ntds_users: ntds_users = mim_string.split("\n42.")[2].split("\nRID :")[1:]
user = ntds_user_txt.split("User :")[1].splitlines()[0].replace("User :", "").strip()
ntlm = ntds_user_txt.split("* Primary\n NTLM :")[1].splitlines()[0].replace("NTLM :", "").strip()
ntlm = ntlm.replace("[hashed secret]", "").strip()
users_dict[user] = {}
if ntlm:
users_dict[user]['ntlm'] = ntlm
for ntds_user_txt in ntds_users:
user = ntds_user_txt.split("User :")[1].splitlines()[0].replace("User :", "").strip()
ntlm = ntds_user_txt.split("* Primary\n NTLM :")[1].splitlines()[0].replace("NTLM :", "").strip()
ntlm = ntlm.replace("[hashed secret]", "").strip()
users_dict[user] = {}
if ntlm:
users_dict[user]['ntlm'] = ntlm
def extract_secrets_from_mimikatz(mim_string): @staticmethod
users_dict = {} def extract_secrets_from_mimikatz(mim_string):
extract_sam_secrets(mim_string, users_dict) users_dict = {}
extract_ntlm_secrets(mim_string, users_dict) MimikatzSecrets.extract_sam_secrets(mim_string, users_dict)
MimikatzSecrets.extract_ntlm_secrets(mim_string, users_dict)
return users_dict
return users_dict

View File

@ -1,7 +1,9 @@
from cc.database import mongo from cc.database import mongo
__author__ = 'maor.rayzin'
class WMIHandler:
class WMIHandler(object):
ADMINISTRATORS_GROUP_KNOWN_SID = '1-5-32-544' ADMINISTRATORS_GROUP_KNOWN_SID = '1-5-32-544'
@ -13,26 +15,29 @@ class WMIHandler:
self.users_info = wmi_info['Win32_UserAccount'] self.users_info = wmi_info['Win32_UserAccount']
self.groups_info = wmi_info['Win32_Group'] self.groups_info = wmi_info['Win32_Group']
self.groups_and_users = wmi_info['Win32_GroupUser'] self.groups_and_users = wmi_info['Win32_GroupUser']
self.products = wmi_info['Win32_Service']
self.services = wmi_info['Win32_Product']
def process_and_handle(self): def process_and_handle_wmi_info(self):
self.add_groups_to_collection() self.add_groups_to_collection()
self.add_users_to_collection() self.add_users_to_collection()
self.create_group_user_connection() self.create_group_user_connection()
self.insert_info_to_mongo()
self.add_admin(self.info_for_mongo[self.ADMINISTRATORS_GROUP_KNOWN_SID], self.monkey_id) self.add_admin(self.info_for_mongo[self.ADMINISTRATORS_GROUP_KNOWN_SID], self.monkey_id)
self.update_admins_retrospective() self.update_admins_retrospective()
self.insert_info_to_mongo() self.update_critical_services()
def update_critical_services(self, wmi_services, wmi_products, machine_id): def update_critical_services(self):
critical_names = ("W3svc", "MSExchangeServiceHost", "MSSQLServer", "dns", 'MSSQL$SQLEXPRESS', 'SQL') critical_names = ("W3svc", "MSExchangeServiceHost", "MSSQLServer", "dns", 'MSSQL$SQLEXPRESS', 'SQL')
mongo.db.monkey.update({'_id': machine_id}, {'$set': {'critical_services': []}}) mongo.db.monkey.update({'_id': self.monkey_id}, {'$set': {'critical_services': []}})
services_names_list = [str(i['Name'])[2:-1] for i in wmi_services] services_names_list = [str(i['Name'])[2:-1] for i in self.services]
products_names_list = [str(i['Name'])[2:-2] for i in wmi_products] products_names_list = [str(i['Name'])[2:-2] for i in self.products]
for name in critical_names: for name in critical_names:
if name in services_names_list or name in products_names_list: if name in services_names_list or name in products_names_list:
mongo.db.monkey.update({'_id': machine_id}, {'$addToSet': {'critical_services': name}}) mongo.db.monkey.update({'_id': self.monkey_id}, {'$addToSet': {'critical_services': name}})
def build_entity_document(self, entity_info, monkey_id=None): def build_entity_document(self, entity_info, monkey_id=None):
general_properties_dict = { general_properties_dict = {

View File

@ -163,7 +163,6 @@ class AppComponent extends AuthComponent {
{this.renderRoute('/run-monkey', <RunMonkeyPage onStatusChange={this.updateStatus}/>)} {this.renderRoute('/run-monkey', <RunMonkeyPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/infection/map', <MapPage onStatusChange={this.updateStatus}/>)} {this.renderRoute('/infection/map', <MapPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/infection/telemetry', <TelemetryPage onStatusChange={this.updateStatus}/>)} {this.renderRoute('/infection/telemetry', <TelemetryPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/pth', <PassTheHashMapPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/start-over', <StartOverPage onStatusChange={this.updateStatus}/>)} {this.renderRoute('/start-over', <StartOverPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/report', <ReportPage onStatusChange={this.updateStatus}/>)} {this.renderRoute('/report', <ReportPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/license', <LicensePage onStatusChange={this.updateStatus}/>)} {this.renderRoute('/license', <LicensePage onStatusChange={this.updateStatus}/>)}

View File

@ -9,9 +9,7 @@ import CollapsibleWellComponent from 'components/report-components/CollapsibleWe
import {Line} from 'rc-progress'; import {Line} from 'rc-progress';
import AuthComponent from '../AuthComponent'; import AuthComponent from '../AuthComponent';
import PassTheHashMapPageComponent from "./PassTheHashMapPage"; import PassTheHashMapPageComponent from "./PassTheHashMapPage";
import SharedCreds from "components/report-components/SharedCreds";
import StrongUsers from "components/report-components/StrongUsers"; import StrongUsers from "components/report-components/StrongUsers";
import SharedAdmins from "components/report-components/SharedAdmins";
let guardicoreLogoImage = require('../../images/guardicore-logo.png'); let guardicoreLogoImage = require('../../images/guardicore-logo.png');
let monkeyLogoImage = require('../../images/monkey-icon.svg'); let monkeyLogoImage = require('../../images/monkey-icon.svg');
@ -47,13 +45,10 @@ class ReportPageComponent extends AuthComponent {
super(props); super(props);
this.state = { this.state = {
report: {}, report: {},
pthreport: {},
pthmap: {},
graph: {nodes: [], edges: []}, graph: {nodes: [], edges: []},
allMonkeysAreDead: false, allMonkeysAreDead: false,
runStarted: true runStarted: true
}; };
this.getPth
} }
componentDidMount() { componentDidMount() {
@ -122,9 +117,7 @@ class ReportPageComponent extends AuthComponent {
.then(res => res.json()) .then(res => res.json())
.then(res => { .then(res => {
this.setState({ this.setState({
report: res, report: res
pthreport: res.pth.info,
pthmap: res.pth.map
}); });
}); });
} }
@ -475,7 +468,7 @@ class ReportPageComponent extends AuthComponent {
<StolenPasswords data={this.state.report.glance.stolen_creds.concat(this.state.report.glance.ssh_keys)}/> <StolenPasswords data={this.state.report.glance.stolen_creds.concat(this.state.report.glance.ssh_keys)}/>
</div> </div>
<div> <div>
<StrongUsers data = {this.state.report.pth.strong_users} /> <StrongUsers data = {this.state.report.glance.strong_users} />
</div> </div>
</div> </div>
); );
@ -495,7 +488,7 @@ class ReportPageComponent extends AuthComponent {
<span>Access credentials <i className="fa fa-lg fa-minus" style={{color: '#0158aa'}}/></span> <b style={{color: '#aeaeae'}}> | </b> <span>Access credentials <i className="fa fa-lg fa-minus" style={{color: '#0158aa'}}/></span> <b style={{color: '#aeaeae'}}> | </b>
</div> </div>
<div> <div>
<PassTheHashMapPageComponent graph={this.state.pthmap} /> <PassTheHashMapPageComponent graph={this.state.report.glance.pth_map} />
</div> </div>
<br /> <br />
</div> </div>

View File

@ -1,42 +0,0 @@
import React from 'react';
import ReactTable from 'react-table'
let renderArray = function(val) {
return <div>{val.map(x => <div>{x}</div>)}</div>;
};
const columns = [
{
Header: 'Shared Admins Between Machines',
columns: [
{ Header: 'Username', accessor: 'username'},
{ Header: 'Domain', accessor: 'domain'},
{ Header: 'Machines', id: 'machines', accessor: x => renderArray(x.machines)},
]
}
];
const pageSize = 10;
class SharedAdminsComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length;
let showPagination = this.props.data.length > pageSize;
return (
<div className="data-table-container">
<ReactTable
columns={columns}
data={this.props.data}
showPagination={showPagination}
defaultPageSize={defaultPageSize}
/>
</div>
);
}
}
export default SharedAdminsComponent;

View File

@ -1,41 +0,0 @@
import React from 'react';
import ReactTable from 'react-table'
let renderArray = function(val) {
console.log(val);
return <div>{val.map(x => <div>{x}</div>)}</div>;
};
const columns = [
{
Header: 'Shared Credentials',
columns: [
{Header: 'Credential Group', id: 'cred_group', accessor: x => renderArray(x.cred_group) }
]
}
];
const pageSize = 10;
class SharedCredsComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length;
let showPagination = this.props.data.length > pageSize;
return (
<div className="data-table-container">
<ReactTable
columns={columns}
data={this.props.data}
showPagination={showPagination}
defaultPageSize={defaultPageSize}
/>
</div>
);
}
}
export default SharedCredsComponent;