forked from p15670423/monkey
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:
parent
9a05d0e87d
commit
822e54f373
|
@ -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')
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
|
@ -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 = []
|
||||
|
||||
|
|
|
@ -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')
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue