Leveraging Altair to Visualize BloodHound Results


Import Libraries

from py2neo import Graph
import plotly.graph_objects as go
import altair as alt

Initialize BloodHound neo4j Database Connection

g = Graph(password=("wardog"))
g
<Graph database=<Database uri='bolt://localhost:7687' secure=False user_agent='py2neo/4.3.0 neobolt/1.7.17 Python/3.7.3-final-0 (linux)'> name='data'>

Retrieve Security Groups with Local Admin Rights over Computers

Now we’ll construct the Cypher query that tells us the name of each security group in Active Directory and the number of computers that group has local admin rights on.

sg_computers_df = g.run("""
MATCH (g:Group)
OPTIONAL MATCH (g)-[:AdminTo]->(c1:Computer)
OPTIONAL MATCH (g)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c2:Computer)
WITH g, COLLECT(c1) + COLLECT(c2) AS tempVar
UNWIND tempVar AS computers
RETURN g.name AS GroupName,COUNT(DISTINCT(computers)) AS AdminRightCount
ORDER BY AdminRightCount DESC
""").to_data_frame()
sg_computers_df
GroupName AdminRightCount
0 DOMAIN ADMINS@CONTOSO.LOCAL 1
1 DOMAIN USERS@CONTOSO.LOCAL 1

Create Horizontal Bar Chart: Most Privileged Active Directory Security Groups

bars = alt.Chart(sg_computers_df, title="Most Privileged Active Directory Security Groups").mark_bar().encode(
    x='AdminRightCount:Q',
    y=alt.Y(
        "GroupName:N",
        sort=alt.EncodingSortField(
            field="AdminRightCount",
            order="descending"
        )
    )
)

text = bars.mark_text(
    align='left',
    baseline='middle',
    dx=3
).encode(
    text='AdminRightCount:Q'
)

(bars + text).properties(height=300)

Create Horizontal Bar Chart: Most Privileged Active Directory Security Groups

users_to_da = g.run("""
MATCH (totalUsers:User {domain:'CONTOSO.LOCAL'})
MATCH p=shortestPath((UsersWithPath:User {domain:'CONTOSO.LOCAL'})-[r*1..]->(g:Group {name:'DOMAIN ADMINS@CONTOSO.LOCAL'}))
WITH COUNT(DISTINCT(totalUsers)) as totalUsers, COUNT(DISTINCT(UsersWithPath)) as UsersWithPath
RETURN 100.0 * UsersWithPath / totalUsers AS percentUsersToDA
""").to_data_frame()
users_to_da
percentUsersToDA
0 93.939394
value_df = users_to_da['percentUsersToDA'].values[0]
value_df
93.93939393939394
fig = go.Figure(go.Indicator(
    domain = {'x': [0, 1], 'y': [0, 1]},
    value = (value_df),
    mode = "gauge+number",
    title = {'text': "Percentage of Users with a Path to Domain Admin"},
    gauge = {'axis': {'range': [None, 100]},
             'steps' : [{'range': [0, 250], 'color': "lightgray"}],
             'threshold' : {'line': {'color': "red", 'width': 4}, 'thickness': 0.75, 'value': 490}}))

fig.show()