forked from p15670423/monkey
Added strong users table in the report and removed old files
This commit is contained in:
parent
c8e547ee8a
commit
ab8ee08b47
|
@ -217,8 +217,6 @@ 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')
|
||||
|
|
|
@ -7,14 +7,6 @@ from bson import ObjectId
|
|||
|
||||
class PTHReportService(object):
|
||||
|
||||
"""
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_duplicated_passwords_nodes():
|
||||
users_cred_groups = []
|
||||
|
@ -153,6 +145,34 @@ class PTHReportService(object):
|
|||
|
||||
return issues
|
||||
|
||||
@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'].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
|
||||
|
||||
|
||||
|
||||
@staticmethod
|
||||
def generate_map_nodes():
|
||||
|
||||
|
@ -188,14 +208,13 @@ class PTHReportService(object):
|
|||
for pair in pairs:
|
||||
edges_list.append(
|
||||
{
|
||||
'from': pair[0],
|
||||
'to': pair[1],
|
||||
'from': pair[1],
|
||||
'to': pair[0],
|
||||
'id': str(uuid.uuid4())
|
||||
}
|
||||
)
|
||||
return edges_list
|
||||
|
||||
|
||||
@staticmethod
|
||||
def generate_edges_tuples(*lists):
|
||||
|
||||
|
@ -205,7 +224,6 @@ class PTHReportService(object):
|
|||
if pair[0] != pair[1]:
|
||||
yield pair
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_report():
|
||||
|
||||
|
@ -216,8 +234,10 @@ class PTHReportService(object):
|
|||
{
|
||||
'report_info':
|
||||
{
|
||||
'strong_users_table': PTHReportService.get_strong_users_on_crit_details(),
|
||||
'pth_issues': issues
|
||||
},
|
||||
|
||||
'pthmap':
|
||||
{
|
||||
'nodes': PTHReportService.generate_map_nodes(),
|
||||
|
|
|
@ -717,6 +717,7 @@ class ReportService:
|
|||
},
|
||||
'pth':
|
||||
{
|
||||
'strong_users': pth_report['report_info']['strong_users_table'],
|
||||
'map': pth_report.get('pthmap'),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -347,8 +347,7 @@ class ReportPageComponent extends AuthComponent {
|
|||
{this.state.report.overview.issues[this.Issue.HADOOP] ?
|
||||
<li>Hadoop/Yarn servers are vulnerable to remote code execution.</li> : null }
|
||||
{this.state.report.overview.issues[this.Issue.PTH_CRIT_SERVICES_ACCESS] ?
|
||||
<li>Credentials of strong users was found on machines and can give access to critical servers
|
||||
(DC, MSSQL, etc..)</li>: null }
|
||||
<li>Mimikatz found login credentials of a user who has admin access to a server defined as critical.</li>: null }
|
||||
</ul>
|
||||
</div>
|
||||
:
|
||||
|
@ -375,11 +374,9 @@ class ReportPageComponent extends AuthComponent {
|
|||
{this.state.report.overview.warnings[this.Warning.TUNNEL] ?
|
||||
<li>Weak segmentation - Machines were able to communicate over unused ports.</li> : null}
|
||||
{this.state.report.overview.warnings[this.Warning.SHARED_LOCAL_ADMIN] ?
|
||||
<li>The monkey has found that some users have administrative rights on several machines.</li> : null}
|
||||
{this.state.report.overview.warnings[this.Warning.SHARED_PASSWORDS_DOMAIN] ?
|
||||
<li>The monkey has found that some users are sharing passwords on domain accounts.</li> : null}
|
||||
<li>Shared local administrator account - Different machines have the same account as a local administrator.</li> : null}
|
||||
{this.state.report.overview.warnings[this.Warning.SHARED_PASSWORDS] ?
|
||||
<li>The monkey has found that some users are sharing passwords.</li> : null}
|
||||
<li>Multiple users have the same password</li> : null}
|
||||
</ul>
|
||||
</div>
|
||||
:
|
||||
|
@ -471,10 +468,15 @@ class ReportPageComponent extends AuthComponent {
|
|||
<div style={{marginBottom: '20px'}}>
|
||||
<ScannedServers data={this.state.report.glance.scanned}/>
|
||||
</div>
|
||||
<div style={{position: 'relative', height: '80vh'}}>
|
||||
{this.generateReportPthMap()}
|
||||
</div>
|
||||
<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.report.pth.strong_users} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -488,6 +490,10 @@ class ReportPageComponent extends AuthComponent {
|
|||
<p>
|
||||
This map visualizes possible attack paths through the network using credential compromise. Paths represent lateral movement opportunities by attackers.
|
||||
</p>
|
||||
<div className="map-legend">
|
||||
<b>Legend: </b>
|
||||
<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} />
|
||||
</div>
|
||||
|
@ -779,7 +785,7 @@ class ReportPageComponent extends AuthComponent {
|
|||
generateSharedLocalAdminsIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Credentials for the user <span className="label label-primary">{issue.username}</span> could be found and the user is an administrator account on more than one machines in the domain.
|
||||
Make sure the right administrator accounts are managing the right machines, and that there isn’t an unintentional local admin sharing.
|
||||
<CollapsibleWellComponent>
|
||||
Here is a list of machines which has this account defined as an administrator:
|
||||
{this.generateInfoBadges(issue.shared_machines)}
|
||||
|
|
|
@ -11,7 +11,6 @@ const columns = [
|
|||
Header: 'Powerful Users',
|
||||
columns: [
|
||||
{ Header: 'Username', accessor: 'username'},
|
||||
{ Header: 'Domain', accessor: 'domain'},
|
||||
{ Header: 'Machines', id: 'machines', accessor: x => renderArray(x.machines)},
|
||||
{ Header: 'Services', id: 'services', accessor: x => renderArray(x.services_names)}
|
||||
]
|
||||
|
|
|
@ -1,259 +0,0 @@
|
|||
from cc.services.pth_report_utils import PassTheHashReport, Machine
|
||||
|
||||
|
||||
class PTHReportService(object):
|
||||
|
||||
"""
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_duplicated_password_nodes(pth):
|
||||
"""
|
||||
|
||||
"""
|
||||
|
||||
usernames_lists = []
|
||||
usernames_per_sid_list = []
|
||||
dups = dict(map(lambda x: (x, len(pth.GetSidsBySecret(x))), pth.GetAllSecrets()))
|
||||
|
||||
for secret, count in sorted(dups.iteritems(), key=lambda (k, v): (v, k), reverse=True):
|
||||
if count <= 1:
|
||||
continue
|
||||
for sid in pth.GetSidsBySecret(secret):
|
||||
if sid:
|
||||
usernames_per_sid_list.append(pth.GetUsernameBySid(sid))
|
||||
|
||||
usernames_lists.append({'cred_group': usernames_per_sid_list})
|
||||
|
||||
return usernames_lists
|
||||
|
||||
@staticmethod
|
||||
def 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
strong_users_crit_list.append(machine)
|
||||
return strong_users_crit_list
|
||||
|
||||
@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 get_duplicated_passwords_issues(pth, password_groups):
|
||||
issues = []
|
||||
previeous_group = []
|
||||
for group in password_groups:
|
||||
username = group['cred_group'][0]
|
||||
if username in previeous_group:
|
||||
continue
|
||||
sid = list(pth.GetSidsByUsername(username.split('\\')[1]))
|
||||
machine_info = pth.GetSidInfo(sid[0])
|
||||
issues.append(
|
||||
{
|
||||
'type': 'shared_passwords',
|
||||
'machine': machine_info.get('hostname').split('.')[0],
|
||||
'shared_with': group['cred_group']
|
||||
}
|
||||
)
|
||||
previeous_group += group['cred_group']
|
||||
|
||||
return issues
|
||||
|
||||
@staticmethod
|
||||
def get_shared_local_admins_issues(shared_admins_machines):
|
||||
issues = []
|
||||
for machine in shared_admins_machines:
|
||||
issues.append(
|
||||
{
|
||||
'type': 'shared_admins',
|
||||
'machine': machine.get('hostname'),
|
||||
'shared_accounts': machine.get('admins_accounts'),
|
||||
'ip': machine.get('ip'),
|
||||
}
|
||||
)
|
||||
|
||||
return issues
|
||||
|
||||
@staticmethod
|
||||
def strong_users_on_crit_issues(strong_users):
|
||||
issues = []
|
||||
for machine in strong_users:
|
||||
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()
|
||||
}
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
@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)
|
||||
|
||||
return nodes_list
|
||||
|
||||
@staticmethod
|
||||
def get_issues_list(issues):
|
||||
issues_dict = {}
|
||||
|
||||
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
|
||||
def get_report():
|
||||
|
||||
issues = []
|
||||
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_by_user(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)
|
||||
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':
|
||||
{
|
||||
'same_password': same_password,
|
||||
'local_admin_shared': local_admin_shared,
|
||||
'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
|
||||
}
|
||||
}
|
||||
return report
|
|
@ -1,958 +0,0 @@
|
|||
import hashlib
|
||||
import binascii
|
||||
import copy
|
||||
import uuid
|
||||
|
||||
from cc.database import mongo
|
||||
from cc.services.node import NodeService
|
||||
|
||||
DsRole_RoleStandaloneWorkstation = 0
|
||||
DsRole_RoleMemberWorkstation = 1
|
||||
DsRole_RoleStandaloneServer = 2
|
||||
DsRole_RoleMemberServer = 3
|
||||
DsRole_RoleBackupDomainController = 4
|
||||
DsRole_RolePrimaryDomainController = 5
|
||||
|
||||
SidTypeUser = 1
|
||||
SidTypeGroup = 2
|
||||
SidTypeDomain = 3
|
||||
SidTypeAlias = 4
|
||||
SidTypeWellKnownGroup = 5
|
||||
SidTypeDeletedAccount = 6
|
||||
SidTypeInvalid = 7
|
||||
SidTypeUnknown = 8
|
||||
SidTypeComputer = 9
|
||||
|
||||
|
||||
def is_group_sid_type(type):
|
||||
return type in (SidTypeGroup, SidTypeAlias, SidTypeWellKnownGroup)
|
||||
|
||||
|
||||
def myntlm(x):
|
||||
hash = hashlib.new('md4', x.encode('utf-16le')).digest()
|
||||
return str(binascii.hexlify(hash))
|
||||
|
||||
|
||||
def cache(foo):
|
||||
def hash(o):
|
||||
if type(o) in (int, float, str, unicode):
|
||||
return repr(o)
|
||||
|
||||
elif type(o) in (type(None),):
|
||||
return "___None___"
|
||||
|
||||
elif type(o) in (list, tuple, set):
|
||||
hashed = tuple([hash(x) for x in o])
|
||||
|
||||
if "NotHashable" in hashed:
|
||||
return "NotHashable"
|
||||
|
||||
return hashed
|
||||
|
||||
elif type(o) == dict:
|
||||
hashed_keys = tuple([hash(k) for k, v in o.iteritems()])
|
||||
hashed_vals = tuple([hash(v) for k, v in o.iteritems()])
|
||||
|
||||
if "NotHashable" in hashed_keys or "NotHashable" in hashed_vals:
|
||||
return "NotHashable"
|
||||
|
||||
return tuple(zip(hashed_keys, hashed_vals))
|
||||
|
||||
elif type(o) == Machine:
|
||||
return o.monkey_guid
|
||||
|
||||
# elif type(o) == PthMap:
|
||||
# return "PthMapSingleton"
|
||||
|
||||
elif type(o) == PassTheHashReport:
|
||||
return "PassTheHashReportSingleton"
|
||||
|
||||
else:
|
||||
assert False, "%s of type %s is not hashable" % (repr(o), type(o))
|
||||
return "NotHashable"
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
hashed = (hash(args), hash(kwargs))
|
||||
|
||||
if "NotHashable" in hashed:
|
||||
return foo(*args, **kwargs)
|
||||
|
||||
if not hasattr(foo, "_mycache_"):
|
||||
foo._mycache_ = dict()
|
||||
|
||||
if hashed not in foo._mycache_.keys():
|
||||
foo._mycache_[hashed] = foo(*args, **kwargs)
|
||||
|
||||
return copy.deepcopy(foo._mycache_[hashed])
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class Machine(object):
|
||||
def __init__(self, monkey_guid):
|
||||
self.monkey_guid = str(monkey_guid)
|
||||
|
||||
self.latest_system_info = mongo.db.telemetry.find(
|
||||
{"telem_type": "system_info_collection", "monkey_guid": self.monkey_guid}).sort([("timestamp", -1)]).limit(
|
||||
1)
|
||||
|
||||
if self.latest_system_info.count() > 0:
|
||||
self.latest_system_info = self.latest_system_info[0]
|
||||
else:
|
||||
self.latest_system_info = None
|
||||
|
||||
self.monkey_info = NodeService.get_monkey_by_guid(self.monkey_guid)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, self.__class__):
|
||||
return self.monkey_guid == other.monkey_guid
|
||||
else:
|
||||
return False
|
||||
|
||||
@cache
|
||||
def GetMimikatzOutput(self):
|
||||
doc = self.latest_system_info
|
||||
|
||||
if not doc:
|
||||
return None
|
||||
|
||||
return doc.get("data").get("mimikatz")
|
||||
|
||||
@cache
|
||||
def GetHostName(self):
|
||||
doc = self.latest_system_info
|
||||
|
||||
for comp in doc.get("data").get("Win32_ComputerSystem", {}):
|
||||
return eval(comp.get("Name"))
|
||||
|
||||
return None
|
||||
|
||||
@cache
|
||||
def GetIp(self):
|
||||
doc = self.latest_system_info
|
||||
|
||||
for addr in doc.get("data").get("network_info", {}).get("networks", {}):
|
||||
return str(addr["addr"])
|
||||
|
||||
return None
|
||||
|
||||
@cache
|
||||
def get_monkey_id(self):
|
||||
doc = self.monkey_info
|
||||
|
||||
return str(doc.get('_id'))
|
||||
|
||||
@cache
|
||||
def GetDomainName(self):
|
||||
doc = self.latest_system_info
|
||||
|
||||
for comp in doc.get("data").get("Win32_ComputerSystem", {}):
|
||||
return eval(comp.get("Domain"))
|
||||
|
||||
return None
|
||||
|
||||
@cache
|
||||
def GetDomainRole(self):
|
||||
doc = self.latest_system_info
|
||||
|
||||
for comp in doc.get("data").get("Win32_ComputerSystem", {}):
|
||||
return comp.get("DomainRole")
|
||||
|
||||
return None
|
||||
|
||||
@cache
|
||||
def IsDomainController(self):
|
||||
return self.GetDomainRole() in (DsRole_RolePrimaryDomainController, DsRole_RoleBackupDomainController)
|
||||
|
||||
#@cache
|
||||
def GetSidByUsername(self, username, domain=None):
|
||||
doc = self.latest_system_info
|
||||
|
||||
for user in doc.get("data").get("Win32_UserAccount", {}):
|
||||
if eval(user.get("Name")) != username:
|
||||
continue
|
||||
|
||||
if user.get("SIDType") != SidTypeUser:
|
||||
continue
|
||||
|
||||
if domain and user.get("Domain") != domain:
|
||||
continue
|
||||
|
||||
return eval(user.get("SID"))
|
||||
|
||||
if not self.IsDomainController():
|
||||
for dc in self.GetDomainControllers():
|
||||
sid = dc.GetSidByUsername(username)
|
||||
|
||||
if sid != None:
|
||||
return sid
|
||||
|
||||
return None
|
||||
|
||||
@cache
|
||||
def GetUsernameBySid(self, sid):
|
||||
info = self.GetSidInfo(sid)
|
||||
|
||||
if not info:
|
||||
return None
|
||||
|
||||
return str(info.get("Domain")) + "\\" + str(info.get("Username"))
|
||||
|
||||
@cache
|
||||
def GetSidInfo(self, sid):
|
||||
doc = self.latest_system_info
|
||||
|
||||
for user in doc.get("data").get("Win32_UserAccount",{}):
|
||||
if eval(user.get("SID")) != sid:
|
||||
continue
|
||||
|
||||
if user.get("SIDType") != SidTypeUser:
|
||||
continue
|
||||
|
||||
return {"Domain": eval(user.get("Domain")),
|
||||
"Username": eval(user.get("Name")),
|
||||
"Disabled": user.get("Disabled") == "true",
|
||||
"PasswordRequired": user.get("PasswordRequired") == "true",
|
||||
"PasswordExpires": user.get("PasswordExpires") == "true",
|
||||
'hostname': doc.get('data').get('hostname'), }
|
||||
|
||||
if not self.IsDomainController():
|
||||
for dc in self.GetDomainControllers():
|
||||
domain = dc.GetSidInfo(sid)
|
||||
|
||||
if domain != None:
|
||||
return domain
|
||||
|
||||
return None
|
||||
|
||||
@cache
|
||||
def GetCriticalServicesInstalled(self):
|
||||
def IsNameOfCriticalService(name):
|
||||
services = ("W3svc", "MSExchangeServiceHost", "MSSQLServer", "dns", 'MSSQL$SQLEXPRESS')
|
||||
services = map(str.lower, services)
|
||||
|
||||
if not name:
|
||||
return False
|
||||
|
||||
name = name.lower()
|
||||
|
||||
return name in services
|
||||
# for ser in services:
|
||||
# if ser in name:
|
||||
# return True
|
||||
|
||||
return False
|
||||
|
||||
doc = self.latest_system_info
|
||||
found = []
|
||||
|
||||
if self.IsDomainController():
|
||||
found.append("Domain Controller")
|
||||
|
||||
for product in doc.get("data").get("Win32_Product", {}):
|
||||
service_name = eval(product.get("Name"))
|
||||
|
||||
if not IsNameOfCriticalService(service_name):
|
||||
continue
|
||||
|
||||
found.append(service_name)
|
||||
|
||||
for service in doc.get("data").get("Win32_Service", {}):
|
||||
service_name = eval(service.get("Name"))
|
||||
|
||||
if not IsNameOfCriticalService(service_name):
|
||||
continue
|
||||
|
||||
if eval(service.get("State")) != "Running":
|
||||
continue
|
||||
|
||||
found.append(service_name)
|
||||
|
||||
return found
|
||||
|
||||
@cache
|
||||
def IsCriticalServer(self):
|
||||
return len(self.GetCriticalServicesInstalled()) > 0
|
||||
|
||||
@cache
|
||||
def GetUsernamesBySecret(self, secret):
|
||||
sam = self.GetLocalSecrets()
|
||||
|
||||
names = set()
|
||||
|
||||
for username, user_secret in sam.iteritems():
|
||||
if secret == user_secret:
|
||||
names.add(username)
|
||||
|
||||
return names
|
||||
|
||||
@cache
|
||||
def GetSidsBySecret(self, secret):
|
||||
usernames = self.GetUsernamesBySecret(secret)
|
||||
return set(map(self.GetSidByUsername, usernames))
|
||||
|
||||
def GetGroupSidByGroupName(self, group_name):
|
||||
doc = self.latest_system_info
|
||||
|
||||
for group in doc.get('data').get("Win32_Group", {}):
|
||||
if eval(group.get("Name")) != group_name:
|
||||
continue
|
||||
|
||||
if not is_group_sid_type(group.get("SIDType")):
|
||||
continue
|
||||
|
||||
return eval(group.get("SID"))
|
||||
|
||||
return None
|
||||
|
||||
def GetUsersByGroupSid(self, sid):
|
||||
doc = self.latest_system_info
|
||||
|
||||
users = dict()
|
||||
|
||||
for group_user in doc.get('data').get("Win32_GroupUser", {}):
|
||||
if eval(group_user.get("GroupComponent", {}).get("SID")) != sid:
|
||||
continue
|
||||
|
||||
if not is_group_sid_type(group_user.get("GroupComponent", {}).get("SIDType")):
|
||||
continue
|
||||
|
||||
if "PartComponent" not in group_user.keys():
|
||||
continue
|
||||
|
||||
if type(group_user.get("PartComponent")) in (str, unicode):
|
||||
# PartComponent is an id to Win32_UserAccount table
|
||||
|
||||
wmi_id = group_user.get("PartComponent")
|
||||
|
||||
if "cimv2:Win32_UserAccount" not in wmi_id:
|
||||
continue
|
||||
|
||||
username = wmi_id.split('cimv2:Win32_UserAccount.Domain="')[1].split('",Name="')[0]
|
||||
domain = wmi_id.split('cimv2:Win32_UserAccount.Domain="')[1].split('",Name="')[1][:-1]
|
||||
|
||||
sid = self.GetSidByUsername(username, domain)
|
||||
users[sid] = username
|
||||
|
||||
else:
|
||||
if group_user.get("PartComponent", {}).get("SIDType") != SidTypeUser:
|
||||
continue
|
||||
|
||||
users[eval(group_user.get("PartComponent", {}).get("SID"))] = eval(group_user.get("PartComponent")
|
||||
.get("Name"))
|
||||
|
||||
return users
|
||||
|
||||
@cache
|
||||
def GetDomainControllersMonkeyGuidByDomainName(self, domain_name):
|
||||
cur = mongo.db.telemetry.find(
|
||||
{"telem_type": "system_info_collection", "data.Win32_ComputerSystem.Domain": "u'%s'" % (domain_name,)})
|
||||
|
||||
GUIDs = set()
|
||||
|
||||
for doc in cur:
|
||||
if not Machine(doc.get("monkey_guid")).IsDomainController():
|
||||
continue
|
||||
|
||||
GUIDs.add(doc.get("monkey_guid"))
|
||||
|
||||
return GUIDs
|
||||
|
||||
def GetLocalAdmins(self):
|
||||
admins = self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators"))
|
||||
|
||||
# debug = self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Users"))
|
||||
# admins.update(debug)
|
||||
|
||||
return admins
|
||||
|
||||
def GetLocalAdminSids(self):
|
||||
return set(self.GetLocalAdmins().keys())
|
||||
|
||||
@cache
|
||||
def GetLocalSids(self):
|
||||
doc = self.latest_system_info
|
||||
|
||||
SIDs = set()
|
||||
|
||||
for user in doc.get('data').get("Win32_UserAccount", {}):
|
||||
if user.get("SIDType") != SidTypeUser:
|
||||
continue
|
||||
|
||||
SIDs.add(eval(user.get("SID")))
|
||||
|
||||
return SIDs
|
||||
|
||||
@cache
|
||||
def GetLocalAdminNames(self):
|
||||
return set(self.GetLocalAdmins().values())
|
||||
|
||||
@cache
|
||||
def GetSam(self):
|
||||
if not self.GetMimikatzOutput():
|
||||
return {}
|
||||
|
||||
mimikatz = self.GetMimikatzOutput()
|
||||
|
||||
if mimikatz.count("\n42.") != 2:
|
||||
return {}
|
||||
|
||||
try:
|
||||
sam_users = mimikatz.split("\n42.")[1].split("\nSAMKey :")[1].split("\n\n")[1:]
|
||||
|
||||
sam = {}
|
||||
|
||||
for sam_user_txt in sam_users:
|
||||
sam_user = dict([map(unicode.strip, line.split(":")) for line in
|
||||
filter(lambda l: l.count(":") == 1, sam_user_txt.splitlines())])
|
||||
|
||||
ntlm = sam_user.get("NTLM")
|
||||
if "[hashed secret]" not in ntlm:
|
||||
continue
|
||||
|
||||
sam[sam_user.get("User")] = ntlm.replace("[hashed secret]", "").strip()
|
||||
|
||||
return sam
|
||||
|
||||
except:
|
||||
return {}
|
||||
|
||||
@cache
|
||||
def GetNtds(self):
|
||||
if not self.GetMimikatzOutput():
|
||||
return {}
|
||||
|
||||
mimikatz = self.GetMimikatzOutput()
|
||||
|
||||
if mimikatz.count("\n42.") != 2:
|
||||
return {}
|
||||
|
||||
ntds_users = mimikatz.split("\n42.")[2].split("\nRID :")[1:]
|
||||
ntds = {}
|
||||
|
||||
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()
|
||||
|
||||
if ntlm:
|
||||
ntds[user] = ntlm
|
||||
|
||||
return ntds
|
||||
|
||||
@cache
|
||||
def GetLocalSecrets(self):
|
||||
sam = self.GetSam()
|
||||
ntds = self.GetNtds()
|
||||
|
||||
secrets = sam.copy()
|
||||
secrets.update(ntds)
|
||||
|
||||
return secrets
|
||||
|
||||
@cache
|
||||
def GetLocalAdminSecrets(self):
|
||||
return set(self.GetLocalAdminCreds().values())
|
||||
|
||||
@cache
|
||||
def GetLocalAdminCreds(self):
|
||||
admin_names = self.GetLocalAdminNames()
|
||||
sam = self.GetLocalSecrets()
|
||||
|
||||
admin_creds = dict()
|
||||
|
||||
for username, secret in sam.iteritems():
|
||||
if username not in admin_names:
|
||||
continue
|
||||
|
||||
admin_creds[username] = secret
|
||||
|
||||
return admin_creds
|
||||
|
||||
@cache
|
||||
def GetCachedSecrets(self):
|
||||
return set(self.GetCachedCreds().values())
|
||||
|
||||
@cache
|
||||
def GetCachedCreds(self):
|
||||
doc = self.latest_system_info
|
||||
|
||||
creds = dict()
|
||||
|
||||
if not self.GetMimikatzOutput():
|
||||
return {}
|
||||
|
||||
mimikatz = self.GetMimikatzOutput()
|
||||
|
||||
for user in mimikatz.split("\n42.")[0].split("Authentication Id")[1:]:
|
||||
username = None
|
||||
secret = None
|
||||
|
||||
for line in user.splitlines():
|
||||
if "User Name" in line:
|
||||
username = line.split(":")[1].strip()
|
||||
|
||||
if ("NTLM" in line or "Password" in line) and "[hashed secret]" in line:
|
||||
secret = line.split(":")[1].replace("[hashed secret]", "").strip()
|
||||
|
||||
if username and secret:
|
||||
creds[username] = secret
|
||||
|
||||
return creds
|
||||
|
||||
@cache
|
||||
def GetDomainControllers(self):
|
||||
domain_name = self.GetDomainName()
|
||||
DCs = self.GetDomainControllersMonkeyGuidByDomainName(domain_name)
|
||||
return map(Machine, DCs)
|
||||
|
||||
def GetDomainAdminsOfMachine(self):
|
||||
DCs = self.GetDomainControllers()
|
||||
|
||||
domain_admins = set()
|
||||
|
||||
for dc in DCs:
|
||||
domain_admins |= set(dc.GetUsersByGroupSid(self.GetGroupSidByGroupName("Domain Admins")).keys())
|
||||
|
||||
return domain_admins
|
||||
|
||||
#@cache
|
||||
def GetAdmins(self):
|
||||
return self.GetLocalAdminSids() # | self.GetDomainAdminsOfMachine()
|
||||
|
||||
@cache
|
||||
def GetAdminNames(self):
|
||||
return set(map(lambda x: self.GetUsernameBySid(x), self.GetAdmins()))
|
||||
|
||||
#@cache
|
||||
def GetCachedSids(self):
|
||||
doc = self.latest_system_info
|
||||
|
||||
SIDs = set()
|
||||
|
||||
for username in doc.get('data').get("credentials", {}):
|
||||
sid = self.GetSidByUsername(username)
|
||||
|
||||
if not sid:
|
||||
sid = "__USERNAME__" + username
|
||||
|
||||
SIDs.add(sid)
|
||||
|
||||
return SIDs
|
||||
|
||||
@cache
|
||||
def GetCachedUsernames(self):
|
||||
doc = self.latest_system_info
|
||||
|
||||
names = set()
|
||||
|
||||
for username in doc.get('data').get("credentials", {}):
|
||||
names.add(username)
|
||||
|
||||
return names
|
||||
|
||||
|
||||
class PassTheHashReport(object):
|
||||
|
||||
def __init__(self):
|
||||
self.vertices = self.GetAllMachines()
|
||||
|
||||
self.machines = map(Machine, self.vertices)
|
||||
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
|
||||
|
||||
|
||||
def GetAllMachines(self):
|
||||
cur = mongo.db.telemetry.find({"telem_type": "system_info_collection"})
|
||||
|
||||
GUIDs = set()
|
||||
|
||||
for doc in cur:
|
||||
GUIDs.add(doc.get("monkey_guid"))
|
||||
|
||||
return GUIDs
|
||||
|
||||
@cache
|
||||
def ReprSidList(self, sid_list, victim):
|
||||
users_list = []
|
||||
|
||||
for sid in sid_list:
|
||||
username = Machine(victim).GetUsernameBySid(sid)
|
||||
|
||||
if username:
|
||||
users_list.append(username)
|
||||
|
||||
return users_list
|
||||
|
||||
@cache
|
||||
def ReprSecretList(self, secret_list, victim):
|
||||
relevant_users_list = []
|
||||
|
||||
for secret in secret_list:
|
||||
relevant_users_list.append(Machine(victim).GetUsernamesBySecret(secret))
|
||||
|
||||
return relevant_users_list
|
||||
|
||||
@staticmethod
|
||||
def __get_edge_label(attacker, victim):
|
||||
attacker_monkey = NodeService.get_monkey_by_guid(attacker)
|
||||
victim_monkey = NodeService.get_monkey_by_guid(victim)
|
||||
|
||||
attacker_label = NodeService.get_monkey_label(attacker_monkey)
|
||||
victim_label = NodeService.get_monkey_label(victim_monkey)
|
||||
|
||||
RIGHT_ARROW = u"\u2192"
|
||||
return "%s %s %s" % (attacker_label, RIGHT_ARROW, victim_label)
|
||||
|
||||
def get_edges_by_sid(self):
|
||||
edges_list = []
|
||||
|
||||
for attacker in self.vertices:
|
||||
cached = list(self.GetCachedSids(Machine(attacker)))
|
||||
|
||||
for victim in self.vertices:
|
||||
if attacker == victim:
|
||||
continue
|
||||
|
||||
admins = list(Machine(victim).GetAdmins())
|
||||
|
||||
cached_admins = [i for i in cached if i in admins]
|
||||
|
||||
if cached_admins:
|
||||
relevant_users_list = self.ReprSidList(cached_admins, victim)
|
||||
edges_list.append(
|
||||
{
|
||||
'from': attacker,
|
||||
'to': victim,
|
||||
'users': relevant_users_list,
|
||||
'_label': PassTheHashReport.__get_edge_label(attacker, victim),
|
||||
'id': str(uuid.uuid4())
|
||||
})
|
||||
|
||||
return edges_list
|
||||
|
||||
@cache
|
||||
def GetEdgesBySamHash(self):
|
||||
edges = set()
|
||||
|
||||
for attacker in self.vertices:
|
||||
cached_creds = set(Machine(attacker).GetCachedCreds().items())
|
||||
|
||||
for victim in self.vertices:
|
||||
if attacker == victim:
|
||||
continue
|
||||
|
||||
admin_creds = set(Machine(victim).GetLocalAdminCreds().items())
|
||||
|
||||
if len(cached_creds & admin_creds) > 0:
|
||||
label = self.ReprSecretList(set(dict(cached_creds & admin_creds).values()), victim)
|
||||
edges.add((attacker, victim, label))
|
||||
|
||||
return edges
|
||||
|
||||
@cache
|
||||
def GetEdgesByUsername(self):
|
||||
edges = set()
|
||||
|
||||
for attacker in self.vertices:
|
||||
cached = Machine(attacker).GetCachedUsernames()
|
||||
|
||||
for victim in self.vertices:
|
||||
if attacker == victim:
|
||||
continue
|
||||
|
||||
admins = Machine(victim).GetAdminNames()
|
||||
|
||||
if len(cached & admins) > 0:
|
||||
edges.add((attacker, victim))
|
||||
|
||||
return edges
|
||||
|
||||
@cache
|
||||
def GetPossibleAttackCountBySid(self, sid):
|
||||
return len(self.GetPossibleAttacksBySid(sid))
|
||||
|
||||
@cache
|
||||
def GetPossibleAttacksByAttacker(self, attacker):
|
||||
attacks = set()
|
||||
|
||||
cached_creds = set(Machine(attacker).GetCachedCreds().items())
|
||||
|
||||
for victim in self.vertices:
|
||||
if attacker == victim:
|
||||
continue
|
||||
|
||||
admin_creds = set(Machine(victim).GetLocalAdminCreds().items())
|
||||
|
||||
if len(cached_creds & admin_creds) > 0:
|
||||
curr_attacks = dict(cached_creds & admin_creds)
|
||||
attacks.add((attacker, victim, curr_attacks))
|
||||
|
||||
return attacks
|
||||
|
||||
@cache
|
||||
def GetPossibleAttacksBySid(self, sid):
|
||||
attacks = set()
|
||||
|
||||
for attacker in self.vertices:
|
||||
tmp = self.GetPossibleAttacksByAttacker(attacker)
|
||||
|
||||
for _, victim, curr_attacks in tmp:
|
||||
for username, secret in curr_attacks.iteritems():
|
||||
if Machine(victim).GetSidByUsername(username) == sid:
|
||||
attacks.add((attacker, victim))
|
||||
|
||||
return attacks
|
||||
|
||||
@cache
|
||||
def GetSecretBySid(self, sid):
|
||||
for m in self.machines:
|
||||
for user, user_secret in m.GetLocalSecrets().iteritems():
|
||||
if m.GetSidByUsername(user) == sid:
|
||||
return user_secret
|
||||
|
||||
return None
|
||||
|
||||
@cache
|
||||
def GetVictimCountBySid(self, sid):
|
||||
return len(self.GetVictimsBySid(sid))
|
||||
|
||||
@cache
|
||||
def GetVictimCountByMachine(self, attacker):
|
||||
return len(self.GetVictimsByAttacker(attacker))
|
||||
|
||||
@cache
|
||||
def GetAttackCountBySecret(self, secret):
|
||||
return len(self.GetAttackersBySecret(secret))
|
||||
|
||||
@cache
|
||||
def GetAllUsernames(self):
|
||||
names = set()
|
||||
|
||||
for sid in self.GetAllSids():
|
||||
names.add(self.GetUsernameBySid(sid))
|
||||
|
||||
return names
|
||||
|
||||
@cache
|
||||
def GetAllSids(self):
|
||||
SIDs = set()
|
||||
|
||||
for m in self.machines:
|
||||
SIDs |= m.GetLocalSids()
|
||||
|
||||
return SIDs
|
||||
|
||||
@cache
|
||||
def GetAllSecrets(self):
|
||||
secrets = set()
|
||||
|
||||
for m in self.machines:
|
||||
for secret in m.GetLocalAdminSecrets():
|
||||
secrets.add(secret)
|
||||
|
||||
return secrets
|
||||
|
||||
@cache
|
||||
def GetUsernameBySid(self, sid):
|
||||
for m in self.machines:
|
||||
username = m.GetUsernameBySid(sid)
|
||||
|
||||
if username:
|
||||
return username
|
||||
|
||||
return None
|
||||
|
||||
@cache
|
||||
def GetSidInfo(self, sid):
|
||||
for m in self.machines:
|
||||
info = m.GetSidInfo(sid)
|
||||
|
||||
if info:
|
||||
return info
|
||||
|
||||
return None
|
||||
|
||||
@cache
|
||||
def GetSidsBySecret(self, secret):
|
||||
SIDs = set()
|
||||
|
||||
for m in self.machines:
|
||||
SIDs |= m.GetSidsBySecret(secret)
|
||||
|
||||
return SIDs
|
||||
|
||||
@cache
|
||||
def GetAllDomainControllers(self):
|
||||
DCs = set()
|
||||
|
||||
for m in self.machines:
|
||||
if m.IsDomainController():
|
||||
DCs.add(m)
|
||||
|
||||
return DCs
|
||||
|
||||
@cache
|
||||
def GetSidsByUsername(self, username):
|
||||
SIDs = set()
|
||||
|
||||
for m in self.machines:
|
||||
sid = m.GetSidByUsername(username)
|
||||
if sid:
|
||||
SIDs.add(sid)
|
||||
|
||||
return SIDs
|
||||
|
||||
@cache
|
||||
def GetVictimsBySid(self, sid):
|
||||
machines = set()
|
||||
|
||||
for m in self.machines:
|
||||
if sid in m.GetAdmins():
|
||||
machines.add(m)
|
||||
|
||||
return machines
|
||||
|
||||
@cache
|
||||
def GetVictimsBySecret(self, secret):
|
||||
machines = set()
|
||||
|
||||
SIDs = self.GetSidsBySecret(secret)
|
||||
|
||||
for m in self.machines:
|
||||
if len(SIDs & m.GetAdmins()) > 0:
|
||||
machines.add(m)
|
||||
|
||||
return machines
|
||||
|
||||
@cache
|
||||
def GetAttackersBySecret(self, secret):
|
||||
machines = set()
|
||||
|
||||
for m in self.machines:
|
||||
if secret in m.GetCachedSecrets():
|
||||
machines.add(m)
|
||||
|
||||
return machines
|
||||
|
||||
@cache
|
||||
def GetAttackersByVictim(self, victim):
|
||||
if type(victim) != unicode:
|
||||
victim = victim.monkey_guid
|
||||
|
||||
attackers = set()
|
||||
|
||||
for edge in self.edges:
|
||||
if edge.get('to', None) == victim:
|
||||
attackers.add(edge.get('from', None))
|
||||
|
||||
return set(map(Machine, attackers))
|
||||
|
||||
@cache
|
||||
def GetAttackersBySid(self, sid):
|
||||
machines = set()
|
||||
|
||||
for m in self.machines:
|
||||
if sid in self.GetCachedSids(m):
|
||||
machines.add(m)
|
||||
|
||||
return machines
|
||||
|
||||
@cache
|
||||
def GetVictimsByAttacker(self, attacker):
|
||||
if type(attacker) != unicode:
|
||||
attacker = attacker.monkey_guid
|
||||
|
||||
victims = set()
|
||||
|
||||
for atck, vic, _ in self.edges:
|
||||
if atck == attacker:
|
||||
victims.add(vic)
|
||||
|
||||
return set(map(Machine, victims))
|
||||
|
||||
@cache
|
||||
def GetInPathCountByVictim(self, victim, already_processed=None):
|
||||
if type(victim) != unicode:
|
||||
victim = victim.monkey_guid
|
||||
|
||||
if not already_processed:
|
||||
already_processed = set([victim])
|
||||
|
||||
count = 0
|
||||
|
||||
for atck, vic, _ in self.edges:
|
||||
if atck == vic:
|
||||
continue
|
||||
|
||||
if vic != victim:
|
||||
continue
|
||||
|
||||
if atck in already_processed:
|
||||
continue
|
||||
|
||||
count += 1
|
||||
|
||||
already_processed.add(atck)
|
||||
count += self.GetInPathCountByVictim(atck, already_processed)
|
||||
|
||||
return count
|
||||
|
||||
@cache
|
||||
def GetCritialServers(self):
|
||||
machines = set()
|
||||
|
||||
for m in self.machines:
|
||||
if m.IsCriticalServer():
|
||||
machines.add(m)
|
||||
|
||||
return machines
|
||||
|
||||
@cache
|
||||
def GetNonCritialServers(self):
|
||||
return set(self.machines) - self.GetCritialServers()
|
||||
|
||||
#@cache
|
||||
def GetCachedSids(self, m):
|
||||
sids = set()
|
||||
tmp = m.GetCachedSids()
|
||||
|
||||
for sid in tmp:
|
||||
if sid.startswith("__USERNAME__"):
|
||||
|
||||
s = self.GetSidsByUsername(sid[len("__USERNAME__"):])
|
||||
if len(s) == 1:
|
||||
sids.add(s.pop())
|
||||
else:
|
||||
sids.add(sid)
|
||||
|
||||
else:
|
||||
sids.add(sid)
|
||||
|
||||
return sids
|
||||
|
||||
@cache
|
||||
def GetThreateningUsersByVictim(self, victim):
|
||||
threatening_users = set()
|
||||
|
||||
for attacker in self.GetAttackersByVictim(victim):
|
||||
# For each attacker, get the cached users and check which of them is an admin on the victim
|
||||
threatening_users |= (self.GetCachedSids(attacker) & victim.GetAdmins())
|
||||
|
||||
return threatening_users
|
||||
|
||||
def GetSharedAdmins(self, m):
|
||||
shared_admins = []
|
||||
|
||||
for other in self.machines:
|
||||
if m == other:
|
||||
continue
|
||||
for sid in m.GetLocalAdminSids():
|
||||
if sid in other.GetLocalAdminSids():
|
||||
shared_admins.append(sid)
|
||||
|
||||
#shared_admins |= (m.GetLocalAdminSids() & other.GetLocalAdminSids())
|
||||
|
||||
shared_admins = [admin for admin in shared_admins if admin not in list(m.GetDomainAdminsOfMachine())]
|
||||
|
||||
return shared_admins
|
|
@ -1,247 +0,0 @@
|
|||
import React from 'react';
|
||||
import {Icon} from 'react-fa';
|
||||
import Toggle from 'react-toggle';
|
||||
import {OverlayTrigger, Tooltip} from 'react-bootstrap';
|
||||
import download from 'downloadjs'
|
||||
import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane';
|
||||
|
||||
class InfMapPreviewPaneComponent extends PreviewPaneComponent {
|
||||
|
||||
osRow(asset) {
|
||||
return (
|
||||
<tr>
|
||||
<th>Operating System</th>
|
||||
<td>{asset.os.charAt(0).toUpperCase() + asset.os.slice(1)}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
ipsRow(asset) {
|
||||
return (
|
||||
<tr>
|
||||
<th>IP Addresses</th>
|
||||
<td>{asset.ip_addresses.map(val => <div key={val}>{val}</div>)}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
servicesRow(asset) {
|
||||
return (
|
||||
<tr>
|
||||
<th>Services</th>
|
||||
<td>{asset.services.map(val => <div key={val}>{val}</div>)}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
accessibleRow(asset) {
|
||||
return (
|
||||
<tr>
|
||||
<th>
|
||||
Accessible From
|
||||
{this.generateToolTip('List of machine which can access this one using a network protocol')}
|
||||
</th>
|
||||
<td>{asset.accessible_from_nodes.map(val => <div key={val}>{val}</div>)}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
statusRow(asset) {
|
||||
return (
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<td>{(asset.dead) ? 'Dead' : 'Alive'}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
forceKill(event, asset) {
|
||||
let newConfig = asset.config;
|
||||
newConfig['alive'] = !event.target.checked;
|
||||
this.authFetch('/api/monkey/' + asset.guid,
|
||||
{
|
||||
method: 'PATCH',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({config: newConfig})
|
||||
});
|
||||
}
|
||||
|
||||
forceKillRow(asset) {
|
||||
return (
|
||||
<tr>
|
||||
<th>
|
||||
Force Kill
|
||||
{this.generateToolTip('If this is on, monkey will die next time it communicates')}
|
||||
</th>
|
||||
<td>
|
||||
<Toggle id={asset.id} checked={!asset.config.alive} icons={false} disabled={asset.dead}
|
||||
onChange={(e) => this.forceKill(e, asset)}/>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
unescapeLog(st) {
|
||||
return st.substr(1, st.length - 2) // remove quotation marks on beginning and end of string.
|
||||
.replace(/\\n/g, "\n")
|
||||
.replace(/\\r/g, "\r")
|
||||
.replace(/\\t/g, "\t")
|
||||
.replace(/\\b/g, "\b")
|
||||
.replace(/\\f/g, "\f")
|
||||
.replace(/\\"/g, '\"')
|
||||
.replace(/\\'/g, "\'")
|
||||
.replace(/\\&/g, "\&");
|
||||
}
|
||||
|
||||
downloadLog(asset) {
|
||||
this.authFetch('/api/log?id=' + asset.id)
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
let timestamp = res['timestamp'];
|
||||
timestamp = timestamp.substr(0, timestamp.indexOf('.'));
|
||||
let filename = res['monkey_label'].split(':').join('-') + ' - ' + timestamp + '.log';
|
||||
let logContent = this.unescapeLog(res['log']);
|
||||
download(logContent, filename, 'text/plain');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
downloadLogRow(asset) {
|
||||
return (
|
||||
<tr>
|
||||
<th>
|
||||
Download Log
|
||||
</th>
|
||||
<td>
|
||||
<a type="button" className="btn btn-primary"
|
||||
disabled={!asset.has_log}
|
||||
onClick={() => this.downloadLog(asset)}>Download</a>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
exploitsTimeline(asset) {
|
||||
if (asset.exploits.length === 0) {
|
||||
return (<div/>);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4 style={{'marginTop': '2em'}}>
|
||||
Exploit Timeline
|
||||
{this.generateToolTip('Timeline of exploit attempts. Red is successful. Gray is unsuccessful')}
|
||||
</h4>
|
||||
<ul className="timeline">
|
||||
{asset.exploits.map(exploit =>
|
||||
<li key={exploit.timestamp}>
|
||||
<div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
|
||||
<div>{new Date(exploit.timestamp).toLocaleString()}</div>
|
||||
<div>{exploit.origin}</div>
|
||||
<div>{exploit.exploiter}</div>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
assetInfo(asset) {
|
||||
return (
|
||||
<div>
|
||||
<table className="table table-condensed">
|
||||
<tbody>
|
||||
{this.osRow(asset)}
|
||||
{this.ipsRow(asset)}
|
||||
{this.servicesRow(asset)}
|
||||
{this.accessibleRow(asset)}
|
||||
</tbody>
|
||||
</table>
|
||||
{this.exploitsTimeline(asset)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
infectedAssetInfo(asset) {
|
||||
return (
|
||||
<div>
|
||||
<table className="table table-condensed">
|
||||
<tbody>
|
||||
{this.osRow(asset)}
|
||||
{this.statusRow(asset)}
|
||||
{this.ipsRow(asset)}
|
||||
{this.servicesRow(asset)}
|
||||
{this.accessibleRow(asset)}
|
||||
{this.forceKillRow(asset)}
|
||||
{this.downloadLogRow(asset)}
|
||||
</tbody>
|
||||
</table>
|
||||
{this.exploitsTimeline(asset)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
scanInfo(edge) {
|
||||
return (
|
||||
<div>
|
||||
<table className="table table-condensed">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Operating System</th>
|
||||
<td>{edge.os.type}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>IP Address</th>
|
||||
<td>{edge.ip_address}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Services</th>
|
||||
<td>{edge.services.map(val => <div key={val}>{val}</div>)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{
|
||||
(edge.exploits.length === 0) ?
|
||||
'' :
|
||||
<div>
|
||||
<h4 style={{'marginTop': '2em'}}>Timeline</h4>
|
||||
<ul className="timeline">
|
||||
{edge.exploits.map(exploit =>
|
||||
<li key={exploit.timestamp}>
|
||||
<div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
|
||||
<div>{new Date(exploit.timestamp).toLocaleString()}</div>
|
||||
<div>{exploit.origin}</div>
|
||||
<div>{exploit.exploiter}</div>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
islandEdgeInfo() {
|
||||
return (
|
||||
<div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getInfoByProps() {
|
||||
switch (this.props.type) {
|
||||
case 'edge':
|
||||
return this.scanInfo(this.props.item);
|
||||
case 'node':
|
||||
return this.props.item.group.includes('monkey', 'manual') ?
|
||||
this.infectedAssetInfo(this.props.item) : this.assetInfo(this.props.item);
|
||||
case 'island_edge':
|
||||
return this.islandEdgeInfo();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default InfMapPreviewPaneComponent;
|
|
@ -1,63 +0,0 @@
|
|||
import React from 'react';
|
||||
import {Icon} from 'react-fa';
|
||||
import Toggle from 'react-toggle';
|
||||
import {OverlayTrigger, Tooltip} from 'react-bootstrap';
|
||||
import download from 'downloadjs'
|
||||
import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane';
|
||||
|
||||
class PthPreviewPaneComponent extends PreviewPaneComponent {
|
||||
nodeInfo(asset) {
|
||||
return (
|
||||
<div>
|
||||
<table className="table table-condensed">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Hostname</th>
|
||||
<td>{asset.hostname}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>IP Addresses</th>
|
||||
<td>{asset.ips.map(val => <div key={val}>{val}</div>)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Services</th>
|
||||
<td>{asset.services.map(val => <div key={val}>{val}</div>)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Compromised Users</th>
|
||||
<td>{asset.users.map(val => <div key={val}>{val}</div>)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
edgeInfo(edge) {
|
||||
return (
|
||||
<div>
|
||||
<table className="table table-condensed">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Compromised Users</th>
|
||||
<td>{edge.users.map(val => <div key={val}>{val}</div>)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getInfoByProps() {
|
||||
switch (this.props.type) {
|
||||
case 'edge':
|
||||
return this.edgeInfo(this.props.item);
|
||||
case 'node':
|
||||
return this.nodeInfo(this.props.item);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default PthPreviewPaneComponent;
|
|
@ -1,58 +0,0 @@
|
|||
import React from 'react';
|
||||
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
||||
import AuthComponent from '../AuthComponent';
|
||||
import {optionsPth, edgeGroupToColorPth, options} from '../map/MapOptions';
|
||||
import PreviewPane from "../map/preview-pane/PreviewPane";
|
||||
import {Col} from "react-bootstrap";
|
||||
import {Link} from 'react-router-dom';
|
||||
import {Icon} from 'react-fa';
|
||||
import PthPreviewPaneComponent from "../map/preview-pane/PthPreviewPane";
|
||||
|
||||
class PassTheHashMapPageComponent extends AuthComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
graph: props.graph,
|
||||
selected: null,
|
||||
selectedType: null
|
||||
};
|
||||
}
|
||||
|
||||
events = {
|
||||
select: event => this.selectionChanged(event)
|
||||
};
|
||||
|
||||
selectionChanged(event) {
|
||||
if (event.nodes.length === 1) {
|
||||
let displayedNode = this.state.graph.nodes.find(
|
||||
function (node) {
|
||||
return node['id'] === event.nodes[0];
|
||||
});
|
||||
this.setState({selected: displayedNode, selectedType: 'node'})
|
||||
}
|
||||
else if (event.edges.length === 1) {
|
||||
let displayedEdge = this.state.graph.edges.find(
|
||||
function (edge) {
|
||||
return edge['id'] === event.edges[0];
|
||||
});
|
||||
this.setState({selected: displayedEdge, selectedType: 'edge'});
|
||||
}
|
||||
else {
|
||||
this.setState({selected: null, selectedType: null});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Col xs={12}>
|
||||
<div style={{height: '70vh'}}>
|
||||
<ReactiveGraph graph={this.state.graph} options={optionsPth} events={this.events}/>
|
||||
</div>
|
||||
</Col>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PassTheHashMapPageComponent;
|
|
@ -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;
|
|
@ -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;
|
|
@ -1,44 +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: 'Powerful Users',
|
||||
columns: [
|
||||
{ Header: 'Username', accessor: 'username'},
|
||||
{ Header: 'Domain', accessor: 'domain'},
|
||||
{ Header: 'Machines', id: 'machines', accessor: x => renderArray(x.machines)},
|
||||
{ Header: 'Services', id: 'services', accessor: x => renderArray(x.services_names)}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const pageSize = 10;
|
||||
|
||||
class StrongUsersComponent 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 StrongUsersComponent;
|
Binary file not shown.
Before Width: | Height: | Size: 20 KiB |
Binary file not shown.
Before Width: | Height: | Size: 19 KiB |
Loading…
Reference in New Issue