forked from p15670423/monkey
* Added information about what info gathered to the report's issues section
This commit is contained in:
parent
af3b5665ce
commit
cdfd6284ee
|
@ -3,7 +3,7 @@ import flask_restful
|
||||||
from cc.auth import jwt_required
|
from cc.auth import jwt_required
|
||||||
from cc.services.pth_report import PTHReportService
|
from cc.services.pth_report import PTHReportService
|
||||||
|
|
||||||
__author__ = "itay.mizeretz"
|
__author__ = "maor.rayzin"
|
||||||
|
|
||||||
|
|
||||||
class PTHReport(flask_restful.Resource):
|
class PTHReport(flask_restful.Resource):
|
||||||
|
|
|
@ -18,7 +18,8 @@ class PTHReportService(object):
|
||||||
continue
|
continue
|
||||||
for sid in pth.GetSidsBySecret(secret):
|
for sid in pth.GetSidsBySecret(secret):
|
||||||
usernames_per_sid_list.append(pth.GetUsernameBySid(sid))
|
usernames_per_sid_list.append(pth.GetUsernameBySid(sid))
|
||||||
usernames_lists.append(usernames_per_sid_list)
|
|
||||||
|
usernames_lists.append({'cred_group': usernames_per_sid_list})
|
||||||
|
|
||||||
return usernames_lists
|
return usernames_lists
|
||||||
|
|
||||||
|
@ -99,24 +100,49 @@ class PTHReportService(object):
|
||||||
'ip': m.GetIp(),
|
'ip': m.GetIp(),
|
||||||
'hostname': m.GetHostName(),
|
'hostname': m.GetHostName(),
|
||||||
'domain': m.GetDomainName(),
|
'domain': m.GetDomainName(),
|
||||||
'services_names': m.GetNonCritialServers(),
|
'services_names': [],
|
||||||
'user_count': count,
|
'user_count': count,
|
||||||
'threatening_users': threatening_users_attackers_dict
|
'threatening_users': threatening_users_attackers_dict
|
||||||
}
|
}
|
||||||
strong_users_non_crit_list.append(machine)
|
strong_users_non_crit_list.append(machine)
|
||||||
return strong_users_non_crit_list
|
return strong_users_non_crit_list
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_duplicated_passwords_issues(pth, password_groups):
|
||||||
|
issues = []
|
||||||
|
issues_dict = {}
|
||||||
|
for group in password_groups:
|
||||||
|
for username in group['cred_group']:
|
||||||
|
sid = list(pth.GetSidsByUsername(username.split('\\')[1]))
|
||||||
|
machine_info = pth.GetSidInfo(sid[0])
|
||||||
|
issues.append(
|
||||||
|
{
|
||||||
|
'type': 'shared_password',
|
||||||
|
'machine': machine_info.get('hostname').split('.')[0],
|
||||||
|
'shared_with': [x for x in group['cred_group'] if x != username],
|
||||||
|
'username': username
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
for issue in issues:
|
||||||
|
machine = issue['machine']
|
||||||
|
if machine not in issues_dict:
|
||||||
|
issues_dict[machine] = []
|
||||||
|
issues_dict[machine].append(issue)
|
||||||
|
|
||||||
|
return issues_dict
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_map_nodes(pth):
|
def generate_map_nodes(pth):
|
||||||
nodes_list = []
|
nodes_list = []
|
||||||
for node_id in pth.vertices:
|
for node_id in pth.vertices:
|
||||||
machine = Machine(node_id)
|
machine = Machine(node_id)
|
||||||
node = {
|
node = {
|
||||||
"id": str(machine.get_monkey_id()),
|
"id": str(node_id),
|
||||||
"label": '{0} : {1}'.format(machine.GetHostName(), machine.GetIp()),
|
"label": '{0} : {1}'.format(machine.GetHostName(), machine.GetIp()),
|
||||||
'group': 'critical' if machine.IsCriticalServer() else 'normal',
|
'group': 'critical' if machine.IsCriticalServer() else 'normal',
|
||||||
'users': list(machine.GetCachedUsernames()),
|
'users': list(machine.GetCachedUsernames()),
|
||||||
'ips': machine.GetIp(),
|
'ips': [machine.GetIp()],
|
||||||
'services': machine.GetCriticalServicesInstalled(),
|
'services': machine.GetCriticalServicesInstalled(),
|
||||||
'hostname': machine.GetHostName()
|
'hostname': machine.GetHostName()
|
||||||
}
|
}
|
||||||
|
@ -127,17 +153,23 @@ class PTHReportService(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_report():
|
def get_report():
|
||||||
pth = PassTheHashReport()
|
pth = PassTheHashReport()
|
||||||
|
same_password = PTHReportService.get_duplicated_password_nodes(pth)
|
||||||
|
local_admin_shared = PTHReportService.get_shared_local_admins_nodes(pth)
|
||||||
|
strong_users_on_crit_services = PTHReportService.get_strong_users_on_crit_services(pth)
|
||||||
|
strong_users_on_non_crit_services = PTHReportService.get_strong_users_on_non_crit_services(pth)
|
||||||
|
issues = PTHReportService.get_duplicated_passwords_issues(pth, same_password)
|
||||||
|
|
||||||
report = \
|
report = \
|
||||||
{
|
{
|
||||||
'report_info':
|
'report_info':
|
||||||
{
|
{
|
||||||
'same_password': PTHReportService.get_duplicated_password_nodes(pth),
|
'same_password': same_password,
|
||||||
'local_admin_shared': PTHReportService.get_shared_local_admins_nodes(pth),
|
'local_admin_shared': local_admin_shared,
|
||||||
'strong_users_on_crit_services': PTHReportService.get_strong_users_on_crit_services(pth),
|
'strong_users_on_crit_services': strong_users_on_crit_services,
|
||||||
'strong_users_on_non_crit_services': PTHReportService.get_strong_users_on_non_crit_services(pth)
|
'strong_users_on_non_crit_services': strong_users_on_non_crit_services,
|
||||||
|
'pth_issues': issues
|
||||||
},
|
},
|
||||||
'map':
|
'pthmap':
|
||||||
{
|
{
|
||||||
'nodes': PTHReportService.generate_map_nodes(pth),
|
'nodes': PTHReportService.generate_map_nodes(pth),
|
||||||
'edges': pth.edges
|
'edges': pth.edges
|
||||||
|
@ -318,4 +350,3 @@ class PTHReportService(object):
|
||||||
# for m in pth.GetAttackersBySecret(secret):
|
# for m in pth.GetAttackersBySecret(secret):
|
||||||
# print """<li><a href="#{ip}">{hostname}</a></li>""".format(ip=m.GetIp(), hostname=m.GetHostName())
|
# print """<li><a href="#{ip}">{hostname}</a></li>""".format(ip=m.GetIp(), hostname=m.GetHostName())
|
||||||
# print """</ul>"""
|
# print """</ul>"""
|
||||||
|
|
||||||
|
|
|
@ -212,7 +212,8 @@ class Machine(object):
|
||||||
"Username": eval(user.get("Name")),
|
"Username": eval(user.get("Name")),
|
||||||
"Disabled": user.get("Disabled") == "true",
|
"Disabled": user.get("Disabled") == "true",
|
||||||
"PasswordRequired": user.get("PasswordRequired") == "true",
|
"PasswordRequired": user.get("PasswordRequired") == "true",
|
||||||
"PasswordExpires": user.get("PasswordExpires") == "true", }
|
"PasswordExpires": user.get("PasswordExpires") == "true",
|
||||||
|
'hostname': doc.get('data').get('hostname'), }
|
||||||
|
|
||||||
if not self.IsDomainController():
|
if not self.IsDomainController():
|
||||||
for dc in self.GetDomainControllers():
|
for dc in self.GetDomainControllers():
|
||||||
|
@ -555,6 +556,7 @@ class Machine(object):
|
||||||
|
|
||||||
return names
|
return names
|
||||||
|
|
||||||
|
|
||||||
class PassTheHashReport(object):
|
class PassTheHashReport(object):
|
||||||
# _instance = None
|
# _instance = None
|
||||||
# def __new__(class_, *args, **kwargs):
|
# def __new__(class_, *args, **kwargs):
|
||||||
|
@ -570,7 +572,7 @@ class PassTheHashReport(object):
|
||||||
self.edges = self.get_edges_by_sid() # Useful for non-cached domain users
|
self.edges = self.get_edges_by_sid() # Useful for non-cached domain users
|
||||||
#self.edges |= self.GetEdgesBySamHash() # This will add edges based only on password hash without caring about username
|
#self.edges |= self.GetEdgesBySamHash() # This will add edges based only on password hash without caring about username
|
||||||
|
|
||||||
@cache
|
|
||||||
def GetAllMachines(self):
|
def GetAllMachines(self):
|
||||||
cur = mongo.db.telemetry.find({"telem_type": "system_info_collection"})
|
cur = mongo.db.telemetry.find({"telem_type": "system_info_collection"})
|
||||||
|
|
||||||
|
@ -610,13 +612,12 @@ class PassTheHashReport(object):
|
||||||
attacker_monkey = NodeService.get_monkey_by_guid(attacker)
|
attacker_monkey = NodeService.get_monkey_by_guid(attacker)
|
||||||
victim_monkey = NodeService.get_monkey_by_guid(victim)
|
victim_monkey = NodeService.get_monkey_by_guid(victim)
|
||||||
|
|
||||||
attacker_label = NodeService.get_node_label(attacker_monkey)
|
attacker_label = NodeService.get_monkey_label(attacker_monkey)
|
||||||
victim_label = NodeService.get_node_label(victim_monkey)
|
victim_label = NodeService.get_monkey_label(victim_monkey)
|
||||||
|
|
||||||
RIGHT_ARROW = u"\u2192"
|
RIGHT_ARROW = u"\u2192"
|
||||||
return "%s %s %s" % (attacker_label, RIGHT_ARROW, victim_label)
|
return "%s %s %s" % (attacker_label, RIGHT_ARROW, victim_label)
|
||||||
|
|
||||||
@cache
|
|
||||||
def get_edges_by_sid(self):
|
def get_edges_by_sid(self):
|
||||||
edges_list = []
|
edges_list = []
|
||||||
|
|
||||||
|
@ -637,7 +638,7 @@ class PassTheHashReport(object):
|
||||||
'to': victim,
|
'to': victim,
|
||||||
'users': relevant_users_list,
|
'users': relevant_users_list,
|
||||||
'_label': PassTheHashReport.__get_edge_label(attacker, victim),
|
'_label': PassTheHashReport.__get_edge_label(attacker, victim),
|
||||||
'id': uuid.uuid4()
|
'id': str(uuid.uuid4())
|
||||||
})
|
})
|
||||||
|
|
||||||
return edges_list
|
return edges_list
|
||||||
|
@ -858,9 +859,9 @@ class PassTheHashReport(object):
|
||||||
|
|
||||||
attackers = set()
|
attackers = set()
|
||||||
|
|
||||||
for atck, vic, _ in self.edges:
|
for edge in self.edges:
|
||||||
if vic == victim:
|
if edge.get('to', None) == victim:
|
||||||
attackers.add(atck)
|
attackers.add(edge.get('from', None))
|
||||||
|
|
||||||
return set(map(Machine, attackers))
|
return set(map(Machine, attackers))
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ class ReportPageComponent extends AuthComponent {
|
||||||
allMonkeysAreDead: false,
|
allMonkeysAreDead: false,
|
||||||
runStarted: true
|
runStarted: true
|
||||||
};
|
};
|
||||||
|
this.getPth
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -117,7 +118,7 @@ class ReportPageComponent extends AuthComponent {
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.setState({
|
this.setState({
|
||||||
pthreport: res.report_info,
|
pthreport: res.report_info,
|
||||||
pthmap: res.map
|
pthmap: res.pthmap
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -389,6 +390,7 @@ class ReportPageComponent extends AuthComponent {
|
||||||
</h3>
|
</h3>
|
||||||
<div>
|
<div>
|
||||||
{this.generateIssues(this.state.report.recommendations.issues)}
|
{this.generateIssues(this.state.report.recommendations.issues)}
|
||||||
|
{this.generateIssues(this.state.pthreport.pth_issues)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -399,6 +401,7 @@ class ReportPageComponent extends AuthComponent {
|
||||||
(100 * this.state.report.glance.exploited.length) / this.state.report.glance.scanned.length;
|
(100 * this.state.report.glance.exploited.length) / this.state.report.glance.scanned.length;
|
||||||
return (
|
return (
|
||||||
<div id="glance">
|
<div id="glance">
|
||||||
|
|
||||||
<h3>
|
<h3>
|
||||||
The Network from the Monkey's Eyes
|
The Network from the Monkey's Eyes
|
||||||
</h3>
|
</h3>
|
||||||
|
@ -452,7 +455,15 @@ class ReportPageComponent extends AuthComponent {
|
||||||
<SharedAdmins data = {this.state.pthreport.local_admin_shared} />
|
<SharedAdmins data = {this.state.pthreport.local_admin_shared} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<StrongUsers data = {this.state.pthreport.strong_users_on_crit_services} />
|
{ /* TODO: use dynamic data */}
|
||||||
|
<StrongUsers data = {[
|
||||||
|
{
|
||||||
|
username: 'SharedLocalAdmin',
|
||||||
|
domain: 'MyDomain',
|
||||||
|
machines: ['hello : 1.2.3.4'],
|
||||||
|
services: ['DC', 'DNS']
|
||||||
|
}
|
||||||
|
]} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -728,6 +739,18 @@ class ReportPageComponent extends AuthComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generateSharedCredsIssue(issue) {
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
Some users are sharing passwords, this should be fixed by changing passwords.
|
||||||
|
<CollapsibleWellComponent>
|
||||||
|
The user <span className="label label-primary">{issue.username}</span> is sharing access password with:
|
||||||
|
{this.generateInfoBadges(issue.shared_with)}.
|
||||||
|
</CollapsibleWellComponent>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
generateTunnelIssue(issue) {
|
generateTunnelIssue(issue) {
|
||||||
return (
|
return (
|
||||||
<li>
|
<li>
|
||||||
|
@ -800,6 +823,9 @@ class ReportPageComponent extends AuthComponent {
|
||||||
case 'cross_segment':
|
case 'cross_segment':
|
||||||
data = this.generateCrossSegmentIssue(issue);
|
data = this.generateCrossSegmentIssue(issue);
|
||||||
break;
|
break;
|
||||||
|
case 'shared_password':
|
||||||
|
data = this.generateSharedCredsIssue(issue);
|
||||||
|
break;
|
||||||
case 'tunnel':
|
case 'tunnel':
|
||||||
data = this.generateTunnelIssue(issue);
|
data = this.generateTunnelIssue(issue);
|
||||||
break;
|
break;
|
||||||
|
|
Loading…
Reference in New Issue