Skip to content

Commit 68e6755

Browse files
committed
BloodHound "owned" extensions added
1 parent 103416a commit 68e6755

File tree

5 files changed

+149
-7
lines changed

5 files changed

+149
-7
lines changed

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
#BloodHound Extensions
2+
The changes in this fork are a product of the ideas here: http://porterhau5.com/blog/extending-bloodhound-track-and-visualize-your-compromise/
3+
4+
This modified version of BloodHound is intended to be used with the Custom Queries and `bh-owned.rb` script found here: https://github.com/porterhau5/BloodHound-Owned
5+
6+
The Pre-Compiled Binaries have not been updated. Using this app will require building from source. See the Quickstart guide here for more details: https://github.com/porterhau5/BloodHound-Owned#quickstart
7+
18
#Downloading BloodHound Binaries
29
Pre-Compiled BloodHound binaries can be found [here](https://github.com/adaptivethreat/BloodHound/releases).
310

src/components/Graph.jsx

+28-1
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,23 @@ export default class GraphContainer extends Component {
459459
var id = data.identity.low
460460
var type = data.labels[0]
461461
var label = data.properties.name
462+
var statement = params.statement
463+
var wave = null
464+
var owned = null
465+
var propswave = data.properties.wave
466+
var propsowned = data.properties.owned
467+
var propsresult = params.props.result
468+
switch (statement) {
469+
case 'MATCH (n)-[r]->(m) WHERE n.wave<=toInt({result}) RETURN n,r,m':
470+
if (propswave == propsresult) wave = propsresult
471+
break;
472+
case 'MATCH (n),(m:Group {name:{result}}),p=shortestPath((n)-[*1..]->(m)) WHERE exists(n.owned) RETURN p':
473+
if (propsowned !== undefined) owned = true
474+
break;
475+
case 'MATCH (n:Group) WHERE n.name =~ {name} WITH n MATCH p=(n)<-[r:MemberOf*1..]-(m) WHERE exists(m.owned) RETURN nodes(p),relationships(p)':
476+
if (propsowned !== undefined) owned = true
477+
break;
478+
}
462479
var node = {
463480
id: id,
464481
type: type,
@@ -472,6 +489,16 @@ export default class GraphContainer extends Component {
472489
y: Math.random()
473490
}
474491

492+
if ((wave !== null) || (owned !== null)){
493+
node.glyphs.push({
494+
'position': 'top-left',
495+
'font': 'FontAwesome',
496+
'content': '\uF0E7',
497+
'fillColor': '#C900FF',
498+
'fontScale': 1.5
499+
})
500+
}
501+
475502
if (label === params.start){
476503
node.start = true
477504
node.glyphs.push({
@@ -836,4 +863,4 @@ export default class GraphContainer extends Component {
836863
this.state.sigmaInstance = sigmaInstance;
837864
this.state.design = design;
838865
}
839-
}
866+
}

src/components/SearchContainer/Tabs/ComputerNodeData.jsx

+43-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ export default class ComputerNodeData extends Component {
2020
firstDegreeLocalAdmin: -1,
2121
groupDelegatedLocalAdmin: -1,
2222
derivativeLocalAdmin: -1,
23-
sessions: -1
23+
sessions: -1,
24+
ownedInWave: "None",
25+
ownedMethod: "None"
2426
}
2527

2628
emitter.on('computerNodeClicked', this.getNodeData.bind(this));
@@ -38,7 +40,9 @@ export default class ComputerNodeData extends Component {
3840
sessions: -1,
3941
firstDegreeLocalAdmin: -1,
4042
groupDelegatedLocalAdmin: -1,
41-
derivativeLocalAdmin: -1
43+
derivativeLocalAdmin: -1,
44+
ownedInWave: "None",
45+
ownedMethod: "None"
4246
})
4347

4448
var s1 = driver.session()
@@ -49,6 +53,8 @@ export default class ComputerNodeData extends Component {
4953
var s6 = driver.session()
5054
var s7 = driver.session()
5155
var s8 = driver.session()
56+
var s9 = driver.session()
57+
var s10 = driver.session()
5258

5359
s1.run("MATCH (a)-[b:AdminTo]->(c:Computer {name:{name}}) RETURN count(a)", {name:payload})
5460
.then(function(result){
@@ -97,6 +103,22 @@ export default class ComputerNodeData extends Component {
97103
this.setState({'sessions':result.records[0]._fields[0].low})
98104
s8.close()
99105
}.bind(this))
106+
107+
s9.run("MATCH (n {name:{name}}) RETURN n.wave", {name:payload})
108+
.then(function(result){
109+
if (result.records[0]._fields[0] != null) {
110+
this.setState({'ownedInWave':result.records[0]._fields[0].low})
111+
}
112+
s9.close()
113+
}.bind(this))
114+
115+
s10.run("MATCH (n {name:{name}}) RETURN n.owned", {name:payload})
116+
.then(function(result){
117+
if (result.records[0]._fields[0] != null) {
118+
this.setState({'ownedMethod':result.records[0]._fields[0]})
119+
}
120+
s10.close()
121+
}.bind(this))
100122
}
101123

102124
render() {
@@ -223,6 +245,25 @@ export default class ComputerNodeData extends Component {
223245
"MATCH (m:Computer {name:{name}})-[r:HasSession]->(n:User) WITH n,r,m WHERE NOT n.name ENDS WITH '$' RETURN n,r,m", {name: this.state.label})
224246
}.bind(this)} />
225247
</dd>
248+
<br />
249+
<dt>
250+
Owned in Wave
251+
</dt>
252+
<dd>
253+
<NodeALink
254+
ready={this.state.ownedInWave !== -1}
255+
value={this.state.ownedInWave}
256+
click={function(){
257+
emitter.emit('query', "OPTIONAL MATCH (n1:User {wave:{wave}}) WITH collect(distinct n1) as c1 OPTIONAL MATCH (n2:Computer {wave:{wave}}) WITH collect(distinct n2) + c1 as c2 OPTIONAL MATCH (n3:Group {wave:{wave}}) WITH c2, collect(distinct n3) + c2 as c3 UNWIND c2 as n UNWIND c3 as m MATCH (n)-[r]->(m) RETURN n,r,m", {wave:this.state.ownedInWave}
258+
,this.state.label)
259+
}.bind(this)} />
260+
</dd>
261+
<dt>
262+
Owned via Method
263+
</dt>
264+
<dd>
265+
{this.state.ownedMethod}
266+
</dd>
226267
</dl>
227268
</div>
228269
);

src/components/SearchContainer/Tabs/GroupNodeData.jsx

+28-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ export default class GroupNodeData extends Component {
1717
derivativeAdminTo: -1,
1818
unrolledMemberOf: -1,
1919
sessions: -1,
20-
foreignGroupMembership: -1
20+
foreignGroupMembership: -1,
21+
ownedInWave: "None"
2122
}
2223

2324
emitter.on('groupNodeClicked', this.getNodeData.bind(this));
@@ -32,7 +33,8 @@ export default class GroupNodeData extends Component {
3233
derivativeAdminTo: -1,
3334
unrolledMemberOf: -1,
3435
sessions: -1,
35-
foreignGroupMembership: -1
36+
foreignGroupMembership: -1,
37+
ownedInWave: "None"
3638
})
3739

3840
var domain = '@' + payload.split('@').last()
@@ -43,6 +45,7 @@ export default class GroupNodeData extends Component {
4345
var s5 = driver.session()
4446
var s6 = driver.session()
4547
var s7 = driver.session()
48+
var s8 = driver.session()
4649

4750
s1.run("MATCH (a)-[b:MemberOf]->(c:Group {name:{name}}) RETURN count(a)", {name:payload})
4851
.then(function(result){
@@ -85,6 +88,16 @@ export default class GroupNodeData extends Component {
8588
this.setState({'foreignGroupMembership':result.records[0]._fields[0].low})
8689
s7.close()
8790
}.bind(this))
91+
92+
s8.run("MATCH (n {name:{name}}) RETURN n.wave", {name:payload})
93+
.then(function(result){
94+
if (result.records[0]._fields[0] != null) {
95+
this.setState({'ownedInWave':result.records[0]._fields[0].low})
96+
} else {
97+
this.setState({'ownedInWave':'None'})
98+
}
99+
s8.close()
100+
}.bind(this))
88101
}
89102

90103
render() {
@@ -183,6 +196,19 @@ export default class GroupNodeData extends Component {
183196
"",this.state.label)
184197
}.bind(this)} />
185198
</dd>
199+
<br />
200+
<dt>
201+
Owned in Wave
202+
</dt>
203+
<dd>
204+
<NodeALink
205+
ready={this.state.ownedInWave !== -1}
206+
value={this.state.ownedInWave}
207+
click={function(){
208+
emitter.emit('query', "OPTIONAL MATCH (n1:User {wave:{wave}}) WITH collect(distinct n1) as c1 OPTIONAL MATCH (n2:Computer {wave:{wave}}) WITH collect(distinct n2) + c1 as c2 OPTIONAL MATCH (n3:Group {wave:{wave}}) WITH c2, collect(distinct n3) + c2 as c3 UNWIND c2 as n UNWIND c3 as m MATCH (n)-[r]->(m) RETURN n,r,m", {wave:this.state.ownedInWave}
209+
,this.state.label)
210+
}.bind(this)} />
211+
</dd>
186212
</dl>
187213
</div>
188214
);

src/components/SearchContainer/Tabs/UserNodeData.jsx

+43-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ export default class UserNodeData extends Component {
2020
firstDegreeLocalAdmin: -1,
2121
groupDelegatedLocalAdmin: -1,
2222
derivativeLocalAdmin: -1,
23-
sessions: -1
23+
sessions: -1,
24+
ownedInWave: "None",
25+
ownedMethod: "None"
2426
}
2527

2628
emitter.on('userNodeClicked', this.getNodeData.bind(this));
@@ -38,7 +40,9 @@ export default class UserNodeData extends Component {
3840
firstDegreeLocalAdmin: -1,
3941
groupDelegatedLocalAdmin: -1,
4042
derivativeLocalAdmin: -1,
41-
sessions: -1
43+
sessions: -1,
44+
ownedInWave: "None",
45+
ownedMethod: "None"
4246
})
4347

4448
var domain = '@' + payload.split('@').last()
@@ -50,6 +54,8 @@ export default class UserNodeData extends Component {
5054
var s5 = driver.session()
5155
var s6 = driver.session()
5256
var s7 = driver.session()
57+
var s8 = driver.session()
58+
var s9 = driver.session()
5359

5460
s1.run("MATCH (n:Group) WHERE NOT n.name ENDS WITH {domain} WITH n MATCH (m:User {name:{name}}) MATCH (m)-[r:MemberOf*1..]->(n) RETURN count(n)", {name:payload, domain: domain})
5561
.then(function(result){
@@ -92,6 +98,22 @@ export default class UserNodeData extends Component {
9298
this.setState({'sessions':result.records[0]._fields[0].low})
9399
s7.close()
94100
}.bind(this))
101+
102+
s8.run("MATCH (n {name:{name}}) RETURN n.wave", {name:payload})
103+
.then(function(result){
104+
if (result.records[0]._fields[0] != null) {
105+
this.setState({'ownedInWave':result.records[0]._fields[0].low})
106+
}
107+
s8.close()
108+
}.bind(this))
109+
110+
s9.run("MATCH (n {name:{name}}) RETURN n.owned", {name:payload})
111+
.then(function(result){
112+
if (result.records[0]._fields[0] != null) {
113+
this.setState({'ownedMethod':result.records[0]._fields[0]})
114+
}
115+
s9.close()
116+
}.bind(this))
95117
}
96118

97119
render() {
@@ -211,6 +233,25 @@ export default class UserNodeData extends Component {
211233
,this.state.label)
212234
}.bind(this)} />
213235
</dd>
236+
<br />
237+
<dt>
238+
Owned in Wave
239+
</dt>
240+
<dd>
241+
<NodeALink
242+
ready={this.state.ownedInWave !== -1}
243+
value={this.state.ownedInWave}
244+
click={function(){
245+
emitter.emit('query', "OPTIONAL MATCH (n1:User {wave:{wave}}) WITH collect(distinct n1) as c1 OPTIONAL MATCH (n2:Computer {wave:{wave}}) WITH collect(distinct n2) + c1 as c2 OPTIONAL MATCH (n3:Group {wave:{wave}}) WITH c2, collect(distinct n3) + c2 as c3 UNWIND c2 as n UNWIND c3 as m MATCH (n)-[r]->(m) RETURN n,r,m", {wave:this.state.ownedInWave}
246+
,this.state.label)
247+
}.bind(this)} />
248+
</dd>
249+
<dt>
250+
Owned via Method
251+
</dt>
252+
<dd>
253+
{this.state.ownedMethod}
254+
</dd>
214255
</dl>
215256
</div>
216257
);

0 commit comments

Comments
 (0)