This commit isn't final. I want to reorganise the code structure a bit,

to make it prettier and readable, also to add docs.

Still need to update the report's text.
This commit is contained in:
maor.rayzin 2018-10-14 17:57:15 +03:00
parent 9a05d0e87d
commit 822e54f373
6 changed files with 124 additions and 169 deletions

View File

@ -197,11 +197,19 @@ class Telemetry(flask_restful.Resource):
Telemetry.create_group_user_connection(info_for_mongo, group_user_dict)
for entity in info_for_mongo.values():
if entity['machine_id']:
# Handling for local entities.
mongo.db.groupsandusers.update({'SID': entity['SID'],
'machine_id': entity['machine_id']}, entity, upsert=True)
else:
# Handlings for domain entities.
if not mongo.db.groupsandusers.find_one({'SID': entity['SID']}):
mongo.db.groupsandusers.insert_one(entity)
else:
# if entity is domain entity, add the monkey id of current machine to secrets_location.
# (found on this machine)
if entity.get('NTLM_secret'):
mongo.db.groupsandusers.update_one({'SID': entity['SID'], 'type': 1},
{'$addToSet': {'secret_location': monkey_id}})
Telemetry.add_admin(info_for_mongo[group_info.ADMINISTRATORS_GROUP_KNOWN_SID], monkey_id)
Telemetry.update_admins_retrospective(info_for_mongo)
@ -209,6 +217,8 @@ class Telemetry(flask_restful.Resource):
telemetry_json['data']['wmi']['Win32_Product'],
monkey_id)
@staticmethod
def update_critical_services(wmi_services, wmi_products, machine_id):
critical_names = ("W3svc", "MSExchangeServiceHost", "MSSQLServer", "dns", 'MSSQL$SQLEXPRESS', 'SQL')

View File

@ -97,6 +97,11 @@ class NodeService:
def get_monkey_label_by_id(monkey_id):
return NodeService.get_monkey_label(NodeService.get_monkey_by_id(monkey_id))
@staticmethod
def get_monkey_critical_services(monkey_id):
critical_services = mongo.db.monkey.find_one({'_id': monkey_id}, {'critical_services': 1}).get('critical_services', [])
return critical_services
@staticmethod
def get_monkey_label(monkey):
label = monkey["hostname"] + " : " + monkey["ip_addresses"][0]

View File

@ -1,3 +1,7 @@
import uuid
from itertools import combinations, product
from cc.services.edge import EdgeService
from cc.services.pth_report_utils import PassTheHashReport, Machine
from cc.database import mongo
from bson import ObjectId
@ -49,7 +53,6 @@ class PTHReportService(object):
@staticmethod
def get_duplicated_passwords_issues():
# TODO: Fix bug if both local and non local account share the same password
user_groups = PTHReportService.get_duplicated_passwords_nodes()
issues = []
users_gathered = []
@ -98,195 +101,130 @@ class PTHReportService(object):
return issues
@staticmethod
def old_get_shared_local_admins_nodes(pth):
dups = dict(map(lambda x: (x, len(pth.GetSharedAdmins(x))), pth.machines))
shared_admin_machines = []
for m, count in sorted(dups.iteritems(), key=lambda (k, v): (v, k), reverse=True):
if count <= 0:
continue
shared_admin_account_list = []
for sid in pth.GetSharedAdmins(m):
shared_admin_account_list.append(pth.GetUsernameBySid(sid))
machine = {
'ip': m.GetIp(),
'hostname': m.GetHostName(),
'domain': m.GetDomainName(),
'services_names': m.GetCriticalServicesInstalled(),
'user_count': count,
'admins_accounts': shared_admin_account_list
def get_strong_users_on_critical_machines_nodes():
crit_machines = {}
pipeline = [
{
'$unwind': '$admin_on_machines'
},
{
'$match': {'type': 1, 'domain_name': {'$ne': None}}
},
{
'$lookup':
{
'from': 'monkey',
'localField': 'admin_on_machines',
'foreignField': '_id',
'as': 'critical_machine'
}
shared_admin_machines.append(machine)
return shared_admin_machines
@staticmethod
def get_strong_users_on_crit_services_by_machine(pth):
threatening = dict(map(lambda x: (x, len(pth.GetThreateningUsersByVictim(x))), pth.GetCritialServers()))
strong_users_crit_list = []
for m, count in sorted(threatening.iteritems(), key=lambda (k, v): (v, k), reverse=True):
if count <= 0:
continue
threatening_users_attackers_dict = {}
for sid in pth.GetThreateningUsersByVictim(m):
username = pth.GetUsernameBySid(sid)
threatening_users_attackers_dict[username] = []
for mm in pth.GetAttackersBySid(sid):
if m == mm:
continue
threatening_users_attackers_dict[username] = mm.GetIp()
machine = {
'ip': m.GetIp(),
'hostname': m.GetHostName(),
'domain': m.GetDomainName(),
'services': m.GetCriticalServicesInstalled(),
'threatening_users': threatening_users_attackers_dict
},
{
'$match': {'critical_machine.critical_services': {'$ne': []}}
},
{
'$unwind': '$critical_machine'
}
strong_users_crit_list.append(machine)
return strong_users_crit_list
]
docs = mongo.db.groupsandusers.aggregate(pipeline)
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']
crit_machines[hostname]['threatening_users'].append(
{'name': str(doc['domain_name']) + '\\' + str(doc['name']),
'creds_location': doc['secret_location']})
return crit_machines
@staticmethod
def get_strong_users_on_crit_services_by_user(pth):
critical_servers = pth.GetCritialServers()
strong_users_dict = {}
for server in critical_servers:
users = pth.GetThreateningUsersByVictim(server)
for sid in users:
username = pth.GetUsernameBySid(sid)
if username not in strong_users_dict:
strong_users_dict[username] = {
'services_names': [],
'machines': []
}
strong_users_dict[username]['username'] = username
strong_users_dict[username]['domain'] = server.GetDomainName()
strong_users_dict[username]['services_names'] += server.GetCriticalServicesInstalled()
strong_users_dict[username]['machines'].append(server.GetHostName())
return strong_users_dict.values()
@staticmethod
def get_strong_users_on_non_crit_services(pth):
threatening = dict(map(lambda x: (x, len(pth.GetThreateningUsersByVictim(x))), pth.GetNonCritialServers()))
strong_users_non_crit_list = []
for m, count in sorted(threatening.iteritems(), key=lambda (k, v): (v, k), reverse=True):
if count <= 0:
continue
threatening_users_attackers_dict = {}
for sid in pth.GetThreateningUsersByVictim(m):
username = pth.GetUsernameBySid(sid)
threatening_users_attackers_dict[username] = []
for mm in pth.GetAttackersBySid(sid):
if m == mm:
continue
threatening_users_attackers_dict[username] = mm.GetIp()
machine = {
'ip': m.GetIp(),
'hostname': m.GetHostName(),
'domain': m.GetDomainName(),
'services_names': [],
'user_count': count,
'threatening_users': threatening_users_attackers_dict
}
strong_users_non_crit_list.append(machine)
return strong_users_non_crit_list
@staticmethod
def strong_users_on_crit_issues(strong_users):
def get_strong_users_on_crit_issues():
issues = []
for machine in strong_users:
crit_machines = PTHReportService.get_strong_users_on_critical_machines_nodes()
for machine in crit_machines:
issues.append(
{
'type': 'strong_users_on_crit',
'machine': machine.get('hostname'),
'services': machine.get('services'),
'ip': machine.get('ip'),
'threatening_users': machine.get('threatening_users').keys()
'machine': machine,
'services': crit_machines[machine].get('critical_services'),
'threatening_users': [i['name'] for i in crit_machines[machine]['threatening_users']]
}
)
return issues
@staticmethod
def get_machine_details(node_id):
machine = Machine(node_id)
node = {}
if machine.latest_system_info:
node = {
"id": str(node_id),
"label": '{0} : {1}'.format(machine.GetHostName(), machine.GetIp()),
'group': 'critical' if machine.IsCriticalServer() else 'normal',
'users': list(machine.GetCachedUsernames()),
'ips': [machine.GetIp()],
'services': machine.GetCriticalServicesInstalled(),
'hostname': machine.GetHostName()
}
return node
def generate_map_nodes():
@staticmethod
def generate_map_nodes(pth):
nodes_list = []
for node_id in pth.vertices:
node = PTHReportService.get_machine_details(node_id)
nodes_list.append(node)
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({
'id': monkey['_id'],
'label': '{0} : {1}'.format(monkey['hostname'], monkey['ip_addresses'][0]),
'group': 'critical' if critical_services else 'normal',
'services': critical_services,
'hostname': monkey['hostname']
})
return nodes_list
@staticmethod
def get_issues_list(issues):
issues_dict = {}
def generate_edge_nodes():
edges_list = []
pipeline = [
{
'$match': {'admin_on_machines': {'$ne': []}, 'secret_location': {'$ne': []}, 'type': 1}
},
{
'$project': {'admin_on_machines': 1, 'secret_location': 1}
}
]
comp_users = mongo.db.groupsandusers.aggregate(pipeline)
for issue in issues:
machine = issue['machine']
if machine not in issues_dict:
issues_dict[machine] = []
issues_dict[machine].append(issue)
for user in comp_users:
pairs = PTHReportService.generate_edges_tuples(user['admin_on_machines'], user['secret_location'])
for pair in pairs:
edges_list.append(
{
'from': pair[0],
'to': pair[1],
'id': str(uuid.uuid4())
}
)
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
return issues_dict
@staticmethod
def get_report():
PTHReportService.get_strong_users_on_critical_machines_nodes()
issues = []
pth = PassTheHashReport()
strong_users_on_crit_services = PTHReportService.get_strong_users_on_crit_services_by_user(pth)
strong_users_on_non_crit_services = PTHReportService.get_strong_users_on_non_crit_services(pth)
issues += PTHReportService.get_duplicated_passwords_issues()
# issues += PTHReportService.get_shared_local_admins_issues(local_admin_shared)
# issues += PTHReportService.strong_users_on_crit_issues(
# PTHReportService.get_strong_users_on_crit_services_by_machine(pth))
report = \
{
'report_info':
{
'strong_users_on_crit_services': strong_users_on_crit_services,
'strong_users_on_non_crit_services': strong_users_on_non_crit_services,
'pth_issues': issues
},
'pthmap':
{
'nodes': PTHReportService.generate_map_nodes(pth),
'edges': pth.edges
'nodes': PTHReportService.generate_map_nodes(),
'edges': PTHReportService.generate_edge_nodes()
}
}
return report

View File

@ -604,6 +604,10 @@ class PassTheHashReport(object):
RIGHT_ARROW = u"\u2192"
return "%s %s %s" % (attacker_label, RIGHT_ARROW, victim_label)
def get_edges_by_sid(self):
edges_list = []

View File

@ -118,10 +118,7 @@ class ReportService:
[NodeService.get_displayed_node_by_id(node['_id'], True) for node in mongo.db.node.find({}, {'_id': 1})] \
+ [NodeService.get_displayed_node_by_id(monkey['_id'], True) for monkey in
mongo.db.monkey.find({}, {'_id': 1})]
for node in nodes:
pth_services = PTHReportService.get_machine_details(NodeService.get_monkey_by_id(node['id'])
.get('guid', None)).get('services', None)
formatted_nodes.append(
{
'label': node['label'],
@ -130,7 +127,7 @@ class ReportService:
(x['hostname'] for x in
(NodeService.get_displayed_node_by_id(edge['from'], True)
for edge in EdgeService.get_displayed_edges_by_to(node['id'], True))),
'services': node['services'] + pth_services if pth_services else []
'services': node['services']
})
logger.info('Scanned nodes generated for reporting')
@ -561,7 +558,8 @@ class ReportService:
ReportService.get_tunnels,
ReportService.get_island_cross_segment_issues,
ReportService.get_azure_issues,
PTHReportService.get_duplicated_passwords_issues
PTHReportService.get_duplicated_passwords_issues,
PTHReportService.get_strong_users_on_crit_issues
]
issues = functools.reduce(lambda acc, issue_gen: acc + issue_gen(), ISSUE_GENERATORS, [])
@ -720,7 +718,6 @@ class ReportService:
'pth':
{
'map': pth_report.get('pthmap'),
'info': pth_report.get('report_info')
}
}

View File

@ -410,18 +410,19 @@ class ReportPageComponent extends AuthComponent {
generateReportRecommendationsSection() {
return (
<div id="recommendations">
<h3>
Recommendations
</h3>
<div>
{this.generateIssues(this.state.report.recommendations.issues)}
</div>
<h3>
Domain related recommendations
</h3>
<div>
{this.generateIssues(this.state.report.recommendations.domain_issues)}
</div>
<h3>
Machine related Recommendations
</h3>
<div>
{this.generateIssues(this.state.report.recommendations.issues)}
</div>
</div>
);
}
@ -474,9 +475,6 @@ class ReportPageComponent extends AuthComponent {
<div style={{marginBottom: '20px'}}>
<StolenPasswords data={this.state.report.glance.stolen_creds.concat(this.state.report.glance.ssh_keys)}/>
</div>
<div>
<StrongUsers data = {this.state.pthreport.strong_users_on_crit_services} />
</div>
</div>
);
}
@ -487,6 +485,9 @@ class ReportPageComponent extends AuthComponent {
<h3>
Credential Map
</h3>
<p>
This map visualizes possible attack paths through the network using credential compromise. Paths represent lateral movement opportunities by attackers.
</p>
<div>
<PassTheHashMapPageComponent graph={this.state.pthmap} />
</div>