Added strong users table in the report and removed old files

This commit is contained in:
maor.rayzin 2018-10-16 12:05:09 +03:00
parent c8e547ee8a
commit ab8ee08b47
17 changed files with 46 additions and 1734 deletions

View File

@ -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')

View File

@ -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(),

View File

@ -717,6 +717,7 @@ class ReportService:
},
'pth':
{
'strong_users': pth_report['report_info']['strong_users_table'],
'map': pth_report.get('pthmap'),
}
}

View File

@ -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 isnt 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)}

View File

@ -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)}
]

View File

@ -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

View File

@ -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

View File

@ -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&nbsp;
{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&nbsp;
{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&nbsp;
{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;

View File

@ -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;

View File

@ -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;

View File

@ -1,42 +0,0 @@
import React from 'react';
import ReactTable from 'react-table'
let renderArray = function(val) {
return <div>{val.map(x => <div>{x}</div>)}</div>;
};
const columns = [
{
Header: 'Shared Admins Between Machines',
columns: [
{ Header: 'Username', accessor: 'username'},
{ Header: 'Domain', accessor: 'domain'},
{ Header: 'Machines', id: 'machines', accessor: x => renderArray(x.machines)},
]
}
];
const pageSize = 10;
class SharedAdminsComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length;
let showPagination = this.props.data.length > pageSize;
return (
<div className="data-table-container">
<ReactTable
columns={columns}
data={this.props.data}
showPagination={showPagination}
defaultPageSize={defaultPageSize}
/>
</div>
);
}
}
export default SharedAdminsComponent;

View File

@ -1,41 +0,0 @@
import React from 'react';
import ReactTable from 'react-table'
let renderArray = function(val) {
console.log(val);
return <div>{val.map(x => <div>{x}</div>)}</div>;
};
const columns = [
{
Header: 'Shared Credentials',
columns: [
{Header: 'Credential Group', id: 'cred_group', accessor: x => renderArray(x.cred_group) }
]
}
];
const pageSize = 10;
class SharedCredsComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length;
let showPagination = this.props.data.length > pageSize;
return (
<div className="data-table-container">
<ReactTable
columns={columns}
data={this.props.data}
showPagination={showPagination}
defaultPageSize={defaultPageSize}
/>
</div>
);
}
}
export default SharedCredsComponent;

View File

@ -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