add pth map to ui

This commit is contained in:
Oran Nadler 2018-03-04 05:22:34 -08:00
parent 6779d4c758
commit 00fe34d431
4 changed files with 390 additions and 0 deletions

View File

@ -17,6 +17,7 @@ from cc.resources.monkey import Monkey
from cc.resources.monkey_configuration import MonkeyConfiguration from cc.resources.monkey_configuration import MonkeyConfiguration
from cc.resources.monkey_download import MonkeyDownload from cc.resources.monkey_download import MonkeyDownload
from cc.resources.netmap import NetMap from cc.resources.netmap import NetMap
from cc.resources.pthmap import PthMap
from cc.resources.node import Node from cc.resources.node import Node
from cc.resources.report import Report from cc.resources.report import Report
from cc.resources.root import Root from cc.resources.root import Root
@ -101,5 +102,7 @@ def init_app(mongo_url):
api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/') api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/')
api.add_resource(Report, '/api/report', '/api/report/') api.add_resource(Report, '/api/report', '/api/report/')
api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/') api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/')
api.add_resource(PthMap, '/api/pthmap', '/api/pthmap/')
return app return app

View File

@ -0,0 +1,327 @@
import flask_restful
from cc.auth import jwt_required
from cc.services.edge import EdgeService
from cc.services.node import NodeService
from cc.database import mongo
import hashlib
import binascii
from pymongo import MongoClient
class PthMap(flask_restful.Resource):
@jwt_required()
def get(self, **kw):
graph = PassTheHashMap()
return \
{
"nodes": [{"id": x} for x in graph.vertices],
"edges": [{"id": str(s) + str(t), "from": s, "to": t} for s, t in graph.edges]
}
DsRole_RoleStandaloneWorkstation = 0
DsRole_RoleMemberWorkstation = 1
DsRole_RoleStandaloneServer = 2
DsRole_RoleMemberServer = 3
DsRole_RoleBackupDomainController = 4
DsRole_RolePrimaryDomainController = 5
def myntlm(x):
hash = hashlib.new('md4', x.encode('utf-16le')).digest()
return str(binascii.hexlify(hash))
class Machine(object):
def __init__(self, monkey_guid):
self.monkey_guid = str(monkey_guid)
def GetMimikatzOutput(self):
cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid})
output = set()
for doc in cur:
output.add(doc["data"]["mimikatz"])
if len(output) == 1:
return output.pop()
return None
def GetHostName(self):
cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid})
names = set()
for doc in cur:
for comp in doc["data"]["Win32_ComputerSystem"]:
names.add(eval(comp["Name"]))
if len(names) == 1:
return names.pop()
return None
def GetIp(self):
cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid})
names = set()
for doc in cur:
for addr in doc["data"]["network_info"]["networks"]:
return str(addr["addr"])
return None
def GetDomainName(self):
cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid})
names = set()
for doc in cur:
for comp in doc["data"]["Win32_ComputerSystem"]:
names.add(eval(comp["Domain"]))
if len(names) == 1:
return names.pop()
return None
def GetDomainRole(self):
cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid})
roles = set()
for doc in cur:
for comp in doc["data"]["Win32_ComputerSystem"]:
roles.add(comp["DomainRole"])
if len(roles) == 1:
return roles.pop()
return None
def GetSidByUsername(self, username):
cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid, "data.Win32_UserAccount.Name":"u'%s'" % (username,)})
SIDs = set()
for doc in cur:
for user in doc["data"]["Win32_UserAccount"]:
if eval(user["Name"]) != username:
continue
SIDs.add(eval(user["SID"]))
if len(SIDs) == 1:
return SIDs.pop()
return None
def GetUsernameBySid(self, sid):
cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid, "data.Win32_UserAccount.SID":"u'%s'" % (sid,)})
names = set()
for doc in cur:
for user in doc["data"]["Win32_UserAccount"]:
if eval(user["SID"]) != sid:
continue
names.add(eval(user["Name"]))
if len(names) == 1:
return names.pop()
return None
def GetGroupSidByGroupName(self, group_name):
cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid, "data.Win32_Group.Name":"u'%s'" % (group_name,)})
SIDs = set()
for doc in cur:
for group in doc["data"]["Win32_Group"]:
if eval(group["Name"]) != group_name:
continue
SIDs.add(eval(group["SID"]))
if len(SIDs) == 1:
return SIDs.pop()
return None
def GetUsersByGroupSid(self, sid):
cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid, "data.Win32_GroupUser.GroupComponent.SID":"u'%s'" % (sid,)})
users = dict()
for doc in cur:
for group_user in doc["data"]["Win32_GroupUser"]:
if eval(group_user["GroupComponent"]["SID"]) != sid:
continue
if "PartComponent" not in group_user.keys():
continue
users[eval(group_user["PartComponent"]["SID"])] = eval(group_user["PartComponent"]["Name"])
return users
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:
for comp in doc["data"]["Win32_ComputerSystem"]:
if ((comp["DomainRole"] != DsRole_RolePrimaryDomainController) and
(comp["DomainRole"] != DsRole_RoleBackupDomainController)):
continue
GUIDs.add(doc["monkey_guid"])
return GUIDs
def GetLocalAdmins(self):
return set(self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators")).keys())
def GetLocalAdminNames(self):
return set(self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators")).values())
def GetLocalAdminSecrets(self):
admin_names = self.GetLocalAdminNames()
sam_users = str(self.GetMimikatzOutput()).split("\nSAMKey :")[1].split("\n\n")[1:]
admin_secrets = set()
for sam_user_txt in sam_users:
sam_user = dict([map(str.strip, line.split(":")) for line in filter(lambda l: l.count(":") == 1, sam_user_txt.splitlines())])
if sam_user["User"] not in admin_names:
continue
admin_secrets.add(sam_user["NTLM"].replace("[hashed secret]", "").strip())
return admin_secrets
def GetCachedSecrets(self):
cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid})
secrets = set()
for doc in cur:
for username in doc["data"]["credentials"]:
user = doc["data"]["credentials"][username]
if "password" in user.keys():
ntlm = myntlm(str(user["password"]))
elif "ntlm_hash" in user.keys():
ntlm = str(user["ntlm_hash"])
else:
continue
secret = hashlib.md5(ntlm.decode("hex")).hexdigest()
secrets.add(secret)
return secrets
def GetDomainAdminsOfMachine(self):
domain_name = self.GetDomainName()
DCs = self.GetDomainControllersMonkeyGuidByDomainName(domain_name)
domain_admins = set()
for dc_monkey_guid in DCs:
domain_admins |= Machine(dc_monkey_guid).GetLocalAdmins()
return domain_admins
def GetAdmins(self):
return self.GetLocalAdmins() | self.GetDomainAdminsOfMachine()
def GetAdminNames(self):
return set(map(lambda x: self.GetUsernameBySid(x), self.GetAdmins()))
def GetCachedSids(self):
cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid})
SIDs = set()
for doc in cur:
for username in doc["data"]["credentials"]:
SIDs.add(self.GetSidByUsername(username))
return SIDs
def GetCachedUsernames(self):
cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid})
SIDs = set()
for doc in cur:
for username in doc["data"]["credentials"]:
SIDs.add(username)
return SIDs
class PassTheHashMap(object):
def __init__(self):
self.vertices = self.GetAllMachines()
self.edges = set()
#self.GenerateEdgesBySid() # Useful for non-cached domain users
self.GenerateEdgesBySamHash() # 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["monkey_guid"])
return GUIDs
def GenerateEdgesBySid(self):
for attacker in self.vertices:
cached = Machine(attacker).GetCachedSids()
for victim in self.vertices:
if attacker == victim:
continue
admins = Machine(victim).GetAdmins()
if len(cached & admins) > 0:
self.edges.add((attacker, victim))
def GenerateEdgesBySamHash(self):
for attacker in self.vertices:
cached = Machine(attacker).GetCachedSecrets()
for victim in self.vertices:
if attacker == victim:
continue
admins = Machine(victim).GetLocalAdminSecrets()
if len(cached & admins) > 0:
self.edges.add((attacker, victim))
def GenerateEdgesByUsername(self):
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:
self.edges.add((attacker, victim))
def Print(self):
print map(lambda x: Machine(x).GetIp(), self.vertices)
print map(lambda x: (Machine(x[0]).GetIp(), Machine(x[1]).GetIp()), self.edges)

View File

@ -7,6 +7,7 @@ import RunServerPage from 'components/pages/RunServerPage';
import ConfigurePage from 'components/pages/ConfigurePage'; import ConfigurePage from 'components/pages/ConfigurePage';
import RunMonkeyPage from 'components/pages/RunMonkeyPage'; import RunMonkeyPage from 'components/pages/RunMonkeyPage';
import MapPage from 'components/pages/MapPage'; import MapPage from 'components/pages/MapPage';
import PassTheHashMapPage from 'components/pages/PassTheHashMapPage';
import TelemetryPage from 'components/pages/TelemetryPage'; import TelemetryPage from 'components/pages/TelemetryPage';
import StartOverPage from 'components/pages/StartOverPage'; import StartOverPage from 'components/pages/StartOverPage';
import ReportPage from 'components/pages/ReportPage'; import ReportPage from 'components/pages/ReportPage';
@ -162,6 +163,7 @@ class AppComponent extends AuthComponent {
{this.renderRoute('/run-monkey', <RunMonkeyPage onStatusChange={this.updateStatus}/>)} {this.renderRoute('/run-monkey', <RunMonkeyPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/infection/map', <MapPage onStatusChange={this.updateStatus}/>)} {this.renderRoute('/infection/map', <MapPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/infection/telemetry', <TelemetryPage onStatusChange={this.updateStatus}/>)} {this.renderRoute('/infection/telemetry', <TelemetryPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/pth', <PassTheHashMapPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/start-over', <StartOverPage onStatusChange={this.updateStatus}/>)} {this.renderRoute('/start-over', <StartOverPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/report', <ReportPage onStatusChange={this.updateStatus}/>)} {this.renderRoute('/report', <ReportPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/license', <LicensePage onStatusChange={this.updateStatus}/>)} {this.renderRoute('/license', <LicensePage onStatusChange={this.updateStatus}/>)}

View File

@ -0,0 +1,58 @@
import React from 'react';
import {Col} from 'react-bootstrap';
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
import AuthComponent from '../AuthComponent';
class PassTheHashMapPageComponent extends AuthComponent {
constructor(props) {
super(props);
this.state = {
graph: {nodes: [], edges: []},
selected: null,
selectedType: null,
killPressed: false,
showKillDialog: false,
telemetry: [],
telemetryLastTimestamp: null
};
}
componentDidMount() {
this.updateMapFromServer();
this.interval = setInterval(this.timedEvents, 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
timedEvents = () => {
this.updateMapFromServer();
};
updateMapFromServer = () => {
this.authFetch('/api/pthmap')
.then(res => res.json())
.then(res => {
this.setState({graph: res});
this.props.onStatusChange();
});
};
render() {
return (
<div>
<Col xs={12} lg={8}>
<h1 className="page-title">3. Pass The Hash Map</h1>
</Col>
<Col xs={12}>
<div style={{height: '80vh'}}>
<ReactiveGraph graph={this.state.graph} />
</div>
</Col>
</div>
);
}
}
export default PassTheHashMapPageComponent;