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.
Returns a dictionary of users with their known credentials.
"""
LOG.info('Getting mimikatz logon information')
if not self._isInit:
return {}
LOG.debug("Running mimikatz collector")

View File

@ -1,125 +1,20 @@
import os
import logging
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
from . import InfoCollector
sys.coinit_flags = 0 # needed for proper destruction of the wmi python module
import infection_monkey.config
from infection_monkey.system_info.mimikatz_collector import MimikatzCollector
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.info('started windows info collector')
__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):
"""
@ -147,8 +42,8 @@ class WindowsInfoCollector(InfoCollector):
self.get_wmi_info()
LOG.debug('finished get_wmi_info')
#self.get_reg_key(r"SYSTEM\CurrentControlSet\Control\Lsa")
self.get_installed_packages()
LOG.debug('Got installed packages')
mimikatz_collector = MimikatzCollector()
mimikatz_info = mimikatz_collector.get_logon_info()
@ -156,39 +51,17 @@ class WindowsInfoCollector(InfoCollector):
if "credentials" in self.info:
self.info["credentials"].update(mimikatz_info)
self.info["mimikatz"] = mimikatz_collector.get_mimikatz_text()
else:
LOG.info('No mimikatz info was gathered')
return self.info
def get_installed_packages(self):
LOG.info('getting installed packages')
self.info["installed_packages"] = os.popen("dism /online /get-packages").read()
self.info["installed_features"] = os.popen("dism /online /get-features").read()
def get_wmi_info(self):
LOG.info('getting wmi info')
for wmi_class_name in WMI_CLASSES:
self.info['wmi'][wmi_class_name] = self.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()
self.info['wmi'][wmi_class_name] = WMIUtils.get_wmi_class(wmi_class_name)

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": {
"class": "logging.handlers.RotatingFileHandler",
"level": "DEBUG",
"level": "INFO",
"formatter": "simple",
"filename": "info.log",
"maxBytes": 10485760,

View File

@ -186,19 +186,11 @@ class Telemetry(flask_restful.Resource):
Telemetry.add_system_info_creds_to_config(creds)
Telemetry.replace_user_dot_with_comma(creds)
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']:
wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets)
wmi_handler.add_groups_to_collection()
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)
wmi_handler.process_and_handle_wmi_info()
@staticmethod
def add_ip_to_ssh_keys(ip, ssh_info):

View File

@ -325,3 +325,7 @@ class NodeService:
@staticmethod
def get_node_hostname(node):
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 combinations, product
from itertools import product
from cc.database import mongo
from bson import ObjectId
from cc.services.node import NodeService
__author__ = 'maor.rayzin'
class PTHReportService(object):
@staticmethod
def get_duplicated_passwords_nodes():
users_cred_groups = []
def __dup_passwords_mongoquery():
pipeline = [
{"$match": {
'NTLM_secret': {
@ -26,74 +26,16 @@ class PTHReportService(object):
}},
{'$match': {'count': {'$gt': 1}}}
]
docs = 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
return mongo.db.groupsandusers.aggregate(pipeline)
@staticmethod
def get_duplicated_passwords_issues():
user_groups = PTHReportService.get_duplicated_passwords_nodes()
issues = []
users_gathered = []
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
def __get_admin_on_machines_format(admin_on_machines):
machines = mongo.db.monkey.find({'_id': {'$in': admin_on_machines}}, {'hostname': 1})
return [i['hostname'] for i in list(machines)]
@staticmethod
def get_shared_admins_nodes():
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 = {}
def __strong_users_on_crit_query():
pipeline = [
{
'$unwind': '$admin_on_machines'
@ -117,13 +59,82 @@ class PTHReportService(object):
'$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:
hostname = str(doc['critical_machine']['hostname'])
if not hostname in crit_machines:
crit_machines[hostname] = {}
crit_machines[hostname]['threatening_users'] = []
crit_machines[hostname]['critical_services'] = doc['critical_machine']['critical_services']
if hostname not in crit_machines:
crit_machines[hostname] = {
'threatening_users': [],
'critical_services': doc['critical_machine']['critical_services']
}
crit_machines[hostname]['threatening_users'].append(
{'name': str(doc['domain_name']) + '\\' + str(doc['name']),
'creds_location': doc['secret_location']})
@ -131,107 +142,92 @@ class PTHReportService(object):
@staticmethod
def get_strong_users_on_crit_issues():
issues = []
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
def get_strong_users_on_crit_details():
table_entries = []
user_details = {}
crit_machines = PTHReportService.get_strong_users_on_critical_machines_nodes()
for machine in crit_machines:
for user in crit_machines[machine]['threatening_users']:
username = user['name']
if username not in user_details:
user_details[username] = {}
user_details[username]['machines'] = []
user_details[username]['services'] = []
user_details[username] = {
'machines': [],
'services': []
}
user_details[username]['machines'].append(machine)
user_details[username]['services'] += crit_machines[machine]['critical_services']
for user in user_details:
table_entries.append(
{
'username': user,
'machines': user_details[user]['machines'],
'services_names': user_details[user]['services']
}
)
return table_entries
return [
{
'username': user,
'machines': user_details[user]['machines'],
'services_names': user_details[user]['services']
} for user in user_details
]
@staticmethod
def generate_map_nodes():
nodes_list = []
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', [])
nodes_list.append({
return [
{
'id': monkey['_id'],
'label': '{0} : {1}'.format(monkey['hostname'], monkey['ip_addresses'][0]),
'group': 'critical' if critical_services else 'normal',
'services': critical_services,
'group': 'critical' if monkey.get('critical_services', []) else 'normal',
'services': monkey.get('critical_services', []),
'hostname': monkey['hostname']
})
return nodes_list
} for monkey in monkeys
]
@staticmethod
def generate_edge_nodes():
def generate_edges():
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:
pairs = PTHReportService.generate_edges_tuples(user['admin_on_machines'], user['secret_location'])
for pair in pairs:
# A list comp, to get all unique pairs of attackers and victims.
for pair in [pair for pair in product(user['admin_on_machines'], user['secret_location'])
if pair[0] != pair[1]]:
edges_list.append(
{
'from': pair[1],
'to': pair[0],
'id': str(uuid.uuid4())
'id': str(pair[1]) + str(pair[0])
}
)
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
def get_pth_map():
return {
'nodes': PTHReportService.generate_map_nodes(),
'edges': PTHReportService.generate_edge_nodes()
'edges': PTHReportService.generate_edges()
}
@staticmethod
def get_report():
pth_map = PTHReportService.get_pth_map()
PTHReportService.get_strong_users_on_critical_machines_nodes()
report = \
{
@ -242,8 +238,8 @@ class PTHReportService(object):
'pthmap':
{
'nodes': PTHReportService.generate_map_nodes(),
'edges': PTHReportService.generate_edge_nodes()
'nodes': pth_map.get('nodes'),
'edges': pth_map.get('edges')
}
}

View File

@ -1,6 +1,5 @@
import itertools
import functools
import pprint
import ipaddress
import logging
@ -160,7 +159,7 @@ class ReportService:
@staticmethod
def get_stolen_creds():
PASS_TYPE_DICT = {'password': 'Clear Password', 'lm_hash': 'LM hash', 'ntlm_hash': 'NTLM hash'}
creds = []
creds = set()
for telem in mongo.db.telemetry.find(
{'telem_type': 'system_info_collection', 'data.credentials': {'$exists': True}},
{'data.credentials': 1, 'monkey_guid': 1}
@ -177,14 +176,9 @@ class ReportService:
'type': PASS_TYPE_DICT[pass_type],
'origin': origin
}
if cred_row not in creds:
creds.append(cred_row)
creds.add(cred_row)
logger.info('Stolen creds generated for reporting')
return creds
@staticmethod
def get_pth_shared_passwords():
pass
return list(creds)
@staticmethod
def get_ssh_keys():
@ -544,7 +538,7 @@ class ReportService:
domain_issues_dict = {}
for issue in issues:
if not issue.get('is_local', True):
machine = issue.get('machine', '').upper()
machine = issue.get('machine').upper()
if machine not in domain_issues_dict:
domain_issues_dict[machine] = []
domain_issues_dict[machine].append(issue)
@ -566,7 +560,7 @@ class ReportService:
issues_dict = {}
for issue in issues:
if issue.get('is_local', True):
machine = issue.get('machine', '').upper()
machine = issue.get('machine').upper()
if machine not in issues_dict:
issues_dict[machine] = []
issues_dict[machine].append(issue)
@ -707,17 +701,14 @@ class ReportService:
'exploited': ReportService.get_exploited(),
'stolen_creds': ReportService.get_stolen_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':
{
'issues': 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):
users_secrets = mim_string.split("\n42.")[1].split("\nSAMKey :")[1].split("\n\n")[1:]
class MimikatzSecrets(object):
if mim_string.count("\n42.") != 2:
return {}
def __init__(self):
# Static class
pass
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] = {}
@staticmethod
def extract_sam_secrets(mim_string, users_dict):
users_secrets = mim_string.split("\n42.")[1].split("\nSAMKey :")[1].split("\n\n")[1:]
ntlm = sam_user.get("NTLM")
if "[hashed secret]" not in ntlm:
continue
if mim_string.count("\n42.") != 2:
return {}
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:
return {}
@staticmethod
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:
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
ntds_users = mim_string.split("\n42.")[2].split("\nRID :")[1:]
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):
users_dict = {}
extract_sam_secrets(mim_string, users_dict)
extract_ntlm_secrets(mim_string, users_dict)
return users_dict
@staticmethod
def extract_secrets_from_mimikatz(mim_string):
users_dict = {}
MimikatzSecrets.extract_sam_secrets(mim_string, users_dict)
MimikatzSecrets.extract_ntlm_secrets(mim_string, users_dict)
return users_dict

View File

@ -1,7 +1,9 @@
from cc.database import mongo
__author__ = 'maor.rayzin'
class WMIHandler:
class WMIHandler(object):
ADMINISTRATORS_GROUP_KNOWN_SID = '1-5-32-544'
@ -13,26 +15,29 @@ class WMIHandler:
self.users_info = wmi_info['Win32_UserAccount']
self.groups_info = wmi_info['Win32_Group']
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_users_to_collection()
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.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')
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]
products_names_list = [str(i['Name'])[2:-2] for i in wmi_products]
services_names_list = [str(i['Name'])[2:-1] for i in self.services]
products_names_list = [str(i['Name'])[2:-2] for i in self.products]
for name in critical_names:
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):
general_properties_dict = {

View File

@ -163,7 +163,6 @@ class AppComponent extends AuthComponent {
{this.renderRoute('/run-monkey', <RunMonkeyPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/infection/map', <MapPage 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('/report', <ReportPage 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 AuthComponent from '../AuthComponent';
import PassTheHashMapPageComponent from "./PassTheHashMapPage";
import SharedCreds from "components/report-components/SharedCreds";
import StrongUsers from "components/report-components/StrongUsers";
import SharedAdmins from "components/report-components/SharedAdmins";
let guardicoreLogoImage = require('../../images/guardicore-logo.png');
let monkeyLogoImage = require('../../images/monkey-icon.svg');
@ -47,13 +45,10 @@ class ReportPageComponent extends AuthComponent {
super(props);
this.state = {
report: {},
pthreport: {},
pthmap: {},
graph: {nodes: [], edges: []},
allMonkeysAreDead: false,
runStarted: true
};
this.getPth
}
componentDidMount() {
@ -122,9 +117,7 @@ class ReportPageComponent extends AuthComponent {
.then(res => res.json())
.then(res => {
this.setState({
report: res,
pthreport: res.pth.info,
pthmap: res.pth.map
report: res
});
});
}
@ -475,7 +468,7 @@ class ReportPageComponent extends AuthComponent {
<StolenPasswords data={this.state.report.glance.stolen_creds.concat(this.state.report.glance.ssh_keys)}/>
</div>
<div>
<StrongUsers data = {this.state.report.pth.strong_users} />
<StrongUsers data = {this.state.report.glance.strong_users} />
</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>
</div>
<div>
<PassTheHashMapPageComponent graph={this.state.pthmap} />
<PassTheHashMapPageComponent graph={this.state.report.glance.pth_map} />
</div>
<br />
</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;