By making a few additions to the source, we can even integrate these properties into the Node Info tab of BloodHound’s UI:
It’s a useful way to document your compromise as you go, and a convenient visual aide when explaining your critical path of compromise to a client.
Note that modifications to the UI like this are only possible if you tweak BloodHound’s source. I’m compiling all of my UI enhancements into a GitHub repo – if you want to try out these additions yourself then you’ll need to install the customized app from the repo.
For those interested, here’s a sample of the changes made to src/components/SearchContainer/Tabs/UserNodeData.jsx to make this happen (diff here):
Finding the Collateral Spread When a Node is Compromised
When we designate a node as owned, we want to see the ripple effect across the network. With our wave property set, we can find those outbound paths like this:
This is similar to the “Find Shortest Paths to Here” idea, but we’re now interested in the paths branching out of a node instead of those coming in. For each new node in the paths we find, we’ll set the wave property equal to the same wave value of the source node(s).
Let’s see this idea in action with the example graphdb included in the BloodHound repo. If you’d like to follow along, I recommend working with a copy of the example graphdb to make starting fresh easier.
We’ll start our theoretical penetration test by firing up Responder and grabbing some NTLMv2 hashes. Assume we were able to crack the hashes and obtain cleartext passwords for two accounts, [email protected] and [email protected] Let’s mark those two accounts as compromised using Cypher:
With these two nodes as our source, let’s use BloodHound’s Raw Query feature to find the other nodes collaterally included in this wave of compromise:
We see two additions – both users are a MemberOf “DOMAIN [email protected]”, and one user is AdminTo “SYSTEM38.INTERNAL.LOCAL”. Neat. Go ahead and add both of those nodes to wave 1 as well:
Simple enough, right? Let’s build on these queries.
Showing Changes in Privilege Gains
We have a way for marking nodes as owned, and we can view the ripple effect of a wave. What happens when we compromise a disjoint set of nodes via some new method? What does the delta in our access look like? It would be terrific if we could see what’s available to us now that wasn’t available to us before.
We can use the same queries as before, but we’ll want to be careful not to overwrite data from previous waves. How do we know which nodes we compromised in previous waves? Each compromised node has a wave property that already exists. If we detect that this property exists, then we know not to include it in the new wave. This can be done in Cypher by negating the EXISTS function:
We’re still looking for the paths branching out (except using the second wave as our starting point), but we don’t want to circle back to nodes we’ve already compromised. The clause AND not(EXISTS(m.wave)) ensures we don’t include any destination nodes with the wave property set.
Let’s see it in context by building off of the previous example. Imagine that the next step of our penetration test involved a password spraying attack against domain users. We found two users with “Spring2017!” on the INTERNAL.LOCAL domain, ZDEVENS and BPICKEREL. Start by marking these two new nodes as owned in wave 2:
Find the spread of compromise, then add those nodes to our second wave:
And now the graph showing the second wave. This represents the delta after our password spraying attack:
That’s handy. Now I know which machines I should go plunder for sensitive documents, local hashes, cached passwords, etc.
Automating the Workflow
It’s tedious to manually run these queries each time a node is compromised. Thankfully, Neo4j’s REST API makes automation possible. With a simple Ruby script, we can leverage the same endpoint used by Export-BloodHoundData to ingest data directly across the network:
I usually start by dumping all of the nodes from the database with -n:
With -e, you can see some of the useful Cypher queries used throughout this blog post (some are slightly modified to improve query performance):
Continuing with our theoretical penetration test, let’s say that we found a juicy Excel spreadsheet which contained credentials for users [email protected] and [email protected] We’ll first create a CSV with the node names and method of compromise like so:
Then we use the -a flag to ingest:
The script will first query the database and determine the latest wave added – in this case it was ‘2’. It then increments it by one so that the incoming additions will be in wave ‘3’. You can override this behavior by setting the -w flag to the preferred wave value.
Once the wave number is determined, the script takes the following steps:
Creates the Cypher queries to set properties on the owned nodes
Creates the Cypher query to find the spread of compromise for the new wave
Wraps it all in JSON
POSTs the request to the REST endpoint
Let’s look at the result for this third wave in BloodHound:
Turns out this wave wasn’t very exciting ¯\_(ツ)_/¯ And we still have to type in our custom query in order to display the graph in BloodHound. Let’s remove that hassle and take it a step further by tweaking the UI and writing some custom queries.
UI Customizations and Custom Queries
BloodHound added a feature in v1.2 to allow for custom queries (more info on CptJesus’s blog). This has the same effect as adding a pre-built query on the Queries tab, but the configuration file has been decoupled from the project’s source code. I found this file in OS X at ~/Library/Application Support/bloodhound/customqueries.json.
Find all owned Domain Admins: Same as the “Find all Domain Admins” query, but instead only show Users with owned property.
Find Shortest Paths from owned node to Domain Admins: Same as the “Find Shortest Paths to Domain Admins” query, but instead only show paths originating from an owned node.
Show wave: Show only the nodes compromised in a selected wave. Useful for focusing in on newly-compromised nodes.
Show delta for wave: Show all compromised nodes up to a selected wave, and will highlight the nodes gained in that wave. Useful for visualizing privilege gains as access expands.
If you’re using the customized BloodHound app, these queries will highlight nodes of interest in the graph as well. Let’s see the custom queries and UI enhancements in context of our example penetration test:
Find all owned Domain Admins
Let’s add two more nodes to our compromise:
And now click on the “Find all owned Domain Admins” custom query:
Boom, we got one. Notice that a magenta lightning bolt appears on the top-left of nodes relevant to our custom query. Like the additions to the Node Info tab, this feature is currently only available in the modified version of BloodHound.
Find Shortest Paths from owned node to Domain Admins
This is the same as the “Find Shortest Paths to Domain Admins”, but we’re focusing on nodes we’ve owned. You’ll see in this graph that the two starting nodes, FILESERVER6 and BGRIFFIN, are marked with our owned icon:
This displays a single, isolated wave. It uses query to present the user with wave values:
It passes the choice to onFinish to display the result. Here’s wave 2 from our example:
Show delta for wave
This displays all waves leading up to the selected wave, and then highlights the nodes from that wave. Like ‘Show wave’, it uses the pop-up picker for wave selection.
If wave 2 is selected, the graph shows waves 1 & 2 then highlights the nodes from wave 2:
If wave 3 is selected, the graph shows waves 1-3 then highlights the nodes from wave 3:
I like this graph for visualizing the changes in privilege gains as it pertains to the greater context of the penetration test. Clients like it too for the same reason – it’s an effective visual aide for explaining the collateral risk of each User or Computer you compromise.
Here’s a couple ideas for taking this a little further. Hopefully I’ll have time to tinker with these in the coming weeks. I’ll post updates here and on Twitter via @porterhau5. Please reach out if you want to explore some of these together! I’d really appreciate some help from those of you who are skilled with front-end development :D
Create a new relationship (maybe “SharesPasswordWith”?) that can be used between User nodes or Computer nodes to show password reuse. A User might use the same password for their normal account and their DA account. A Computer might use the same local admin password as another Computer’s local admin. Expressing this in the form of a relationship allows us to leverage BloodHound’s pre-built queries.
Instead of running custom queries through the Queries tab, have a slider on the main dashboard that can step through waves.
Define a “Critical Path to Compromise” (CPTC): the exact path taken through the network to go from A to Z. Add property to nodes indicating their involvement in the CPTC. Write custom query to show this path specifically and filter out the rest of the noise. Mostly useful for presenting/explaining to client.
Add more options when a node is right-clicked. For example, “Shortest Paths from Here” or “Add to wave X”.
Make “Owned in Wave” and “Owned via Method” values fillable from the UI. Ditch the need for an external script to ingest data.
General query optimizations to help with scalability & speed. The graphs can get a little messy when a DA account is obtained.