forked from p34709852/monkey
add pth map to ui
This commit is contained in:
parent
6779d4c758
commit
00fe34d431
|
@ -17,6 +17,7 @@ from cc.resources.monkey import Monkey
|
|||
from cc.resources.monkey_configuration import MonkeyConfiguration
|
||||
from cc.resources.monkey_download import MonkeyDownload
|
||||
from cc.resources.netmap import NetMap
|
||||
from cc.resources.pthmap import PthMap
|
||||
from cc.resources.node import Node
|
||||
from cc.resources.report import Report
|
||||
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(Report, '/api/report', '/api/report/')
|
||||
api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/')
|
||||
|
||||
api.add_resource(PthMap, '/api/pthmap', '/api/pthmap/')
|
||||
|
||||
return app
|
||||
|
|
|
@ -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)
|
|
@ -7,6 +7,7 @@ import RunServerPage from 'components/pages/RunServerPage';
|
|||
import ConfigurePage from 'components/pages/ConfigurePage';
|
||||
import RunMonkeyPage from 'components/pages/RunMonkeyPage';
|
||||
import MapPage from 'components/pages/MapPage';
|
||||
import PassTheHashMapPage from 'components/pages/PassTheHashMapPage';
|
||||
import TelemetryPage from 'components/pages/TelemetryPage';
|
||||
import StartOverPage from 'components/pages/StartOverPage';
|
||||
import ReportPage from 'components/pages/ReportPage';
|
||||
|
@ -162,6 +163,7 @@ class AppComponent extends AuthComponent {
|
|||
{this.renderRoute('/run-monkey', <RunMonkeyPage onStatusChange={this.updateStatus}/>)}
|
||||
{this.renderRoute('/infection/map', <MapPage 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('/report', <ReportPage onStatusChange={this.updateStatus}/>)}
|
||||
{this.renderRoute('/license', <LicensePage onStatusChange={this.updateStatus}/>)}
|
||||
|
|
|
@ -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;
|
Loading…
Reference in New Issue