I Ran BloodBash Against an Old, Abandoned Azure Tenant: An Honest Review
I pointed BloodBash, a free offline BloodHound alternative, at an old company tenant I still control but stopped watching years ago. It found 4 standing Global Administrators nobody remembered, but only after I worked around an undocumented licensing wall, and it completely missed a real privilege path I had to find by hand.

I ran a free, open-source attack-path analyzer against an Azure tenant I have not actively managed in years: a leftover from an old company, the domain long since expired and re-registered by someone else, the Entra ID directory still quietly sitting there under my account. Ten minutes in, the tool's own data collector refused to hand over a single user, not because I lacked permission, but because of a Microsoft Graph licensing rule that has nothing to do with who you are. Working around it by hand is what actually produced the interesting result: four standing Global Administrators on a tenant nobody has looked at since the business behind it stopped operating.
This is the honest account of running BloodBash: what it is, what it takes to get real data into it, the wall I hit and how I got past it, and what it did and did not find.
What BloodBash actually is
BloodBash is an open-source tool from SquidSec that reads the same collector output as BloodHound, the well-known attack-path graphing tool for Active Directory and Entra ID, and analyzes it offline. No Neo4j database, no web UI, no server to stand up. You point it at a folder of JSON and it prints a prioritized list of findings straight to your terminal.
The pitch is speed and simplicity. BloodHound is the standard for a reason: it models the real question attackers and defenders both care about, which is not "does this account have admin rights" but "how many hops is any given account from Domain Admin or Global Administrator." But standing up BloodHound means running its full graph database stack. BloodBash skips that. It is meant for a quick pass: collect data once, get answers in your terminal, move on.
BloodBash itself does not touch your environment. It only ever reads files that something else already collected. That collection step is where almost all of the actual friction lives, and it comes from a completely different, unrelated project.
How it works under the hood
The workflow has two separate stages, built by two separate teams:
flowchart TB
A[AzureHound or SharpHound<br/>data collector] -->|reads directory via<br/>Microsoft Graph / LDAP| B[JSON export]
B --> C[BloodBash<br/>offline analyzer]
C --> D[Graph built in memory<br/>no database]
D --> E[Prioritized findings:<br/>privileged roles, credential paths,<br/>attack paths to high-value targets]For an on-premises Active Directory environment, the collector is SharpHound. For Entra ID and Azure, it is AzureHound, built by SpecterOps (the same team behind BloodHound itself). Both walk the directory read-only: users, groups, devices, app registrations, service principals, role assignments, group memberships. Nothing is modified. AzureHound then hands that JSON to BloodBash, which builds an in-memory graph and runs a fixed set of checks: who holds privileged roles, which service principals can control which apps, which accounts are guests with elevated rights, and what the shortest path is from any given principal to a high-value target like Global Administrator.
That is the whole idea, and on paper it needs nothing you do not already have if you are a tenant admin. In practice, getting real data into that pipeline is where I spent most of my time.
What it actually takes to run
BloodBash ships as a standalone binary, but only for Linux and Windows. There is no macOS build, so on a Mac the only path is running it from source:
That part is minor friction, a few minutes, no different from installing any other Python security tool. AzureHound is where things got interesting. It supports several ways to authenticate, including an app registration with a client secret, but if you already have an active Azure CLI session, you can hand it a bare access token instead, no app registration required:
That command ran cleanly and, in under ten seconds, pulled 741 service principals, 145 role definitions, 22 app registrations, 6 devices, and 1 security group out of the tenant. It looked like a complete collection. It was not.
The wall nobody documents
AzureHound's list command is supposed to also enumerate every user in the directory. Mine came back with zero. Running the users collector in isolation surfaced the real reason:
That is Microsoft Graph, not AzureHound, refusing the request. AzureHound's user query asks for a sign-in activity field alongside the basic user properties, and Graph gates that specific field behind an Entra ID Premium P1 or P2 license. It has nothing to do with whether the account making the request is a Global Administrator. I checked the tenant's licenses directly, and it genuinely had no premium SKU active, just a leftover free Microsoft Flow license. I went looking for a free P1 trial to activate as a fix, and it was not offered: tenants get that trial once, and this one had already used it, years ago.
This is the kind of gap that turns a five-minute test into something longer, and it is not documented anywhere prominent in either project's README. If your tenant does not carry a premium license, and a lot of small businesses, personal tenants, and abandoned ones do not, the default AzureHound collection will silently hand BloodBash zero users, and BloodBash will have nothing to build attack paths from.
Groups had a similar-looking gap that turned out to be correct behavior, not a bug: AzureHound returned 1 group out of 7 that actually existed in the tenant. The other 6 were mail-enabled distribution lists and Microsoft 365 Groups, not security-enabled groups, and attack-path tooling correctly ignores those because they cannot hold permissions the way a security group can.
The workaround
The blocked field lives specifically in AzureHound's own query, not in Microsoft Graph's basic user endpoint. A plain request for user objects, without the sign-in activity field AzureHound insists on including, works on any tenant regardless of license tier:
That returned all 44 real users with their object IDs, no premium license required. From there it was a matter of shaping that response into the exact JSON structure AzureHound would have produced (BloodBash ships a sample AzureHound export in its repo, which is the fastest way to confirm the exact field names a given object type expects) and merging those records into the collection I already had. Feeding that combined file to BloodBash's full analysis mode is what actually produced results.
None of this is exotic. It is one Graph API call and a JSON reshape. But it means the realistic path to a complete AzureHound/BloodBash run against a tenant without a premium license is not "run the tool," it is "run the tool, notice it silently dropped your users, and go write a small script." That is worth knowing before you plan a fixed amount of time for this.
What it found on a tenant nobody was watching
With real user data in place, BloodBash's full attack-path analysis surfaced three things, and only one of them was actually alarming.
Four standing Global Administrators, years after anyone was paying attention
The tenant had 4 accounts holding the Global Administrator role: my current admin identity, an old guest identity created back when I first set up the tenant, a stale test account, and a former collaborator's account that was never removed after they stopped working on the project. Nobody has reviewed this tenant's admin roster since the business behind it stopped operating and the domain lapsed to someone else. This is exactly the kind of thing attack-path tooling is built to catch: not a single dramatic misconfiguration, but privilege that quietly accumulates and never gets cleaned up because nobody's job is to check. If this were a live production tenant instead of an abandoned one, four standing Global Admins with no active review process would be a real finding worth acting on immediately.
A finding that is probably noise
BloodBash also flagged a credential-control path between two service principals: "Azure Media Service" and "Office 365 SharePoint Online," described as an ownership relationship worth investigating. Neither of those is anything this tenant configured. Both are Microsoft's own first-party service principals that get silently provisioned into every Entra ID tenant on Earth as part of the default Microsoft 365 plumbing. Out of 741 service principals the collection returned, the overwhelming majority are Microsoft's own, not anything a tenant admin created, and the tool does not visibly distinguish "default Microsoft infrastructure" from "something an admin configured." A reader without outside knowledge of what these first-party apps are would have no way to tell this apart from a real finding, and would either waste time investigating a non-issue or, worse, learn to distrust the tool's output generally after chasing a few of these down.
What it never saw at all
Separately, using nothing but a plain az role assignment list against the subscription, I found two app registrations holding standing Contributor rights at the Azure subscription level: one tied to Let's Encrypt certificate automation, one tied to internal automation scripts. Neither showed up anywhere in BloodBash's output. The reason is structural, not a bug to be fixed in a point release: BloodBash's graph is built entirely from the Entra ID directory-role data AzureHound collects through Microsoft Graph. Azure subscription-level role assignments live in a completely different API, Azure Resource Manager, and neither AzureHound nor BloodBash reaches into it. An app registration sitting on standing Contributor for your entire subscription is a real, meaningful privilege path (from that app's credentials to full control of every resource in the subscription), and it is invisible to this tool by design. You would need to check ARM role assignments separately, every time, as a matter of routine.
What you would likely find on a live, active tenant
My tenant was dead, so the finding was extreme: four forgotten Global Admins on a directory nobody had opened in years. Most readers pointing this at their own environment are not sitting on an abandoned tenant, they are running it against something actively in use, and the findings look less dramatic but come from the same handful of patterns. Based on what BloodBash's Azure and Entra checks actually look for:
- Standing privileged roles on accounts that should not have them. Not just who holds Global Administrator or Privileged Role Administrator right now, but whether that access is a permanent (standing) assignment versus something granted just-in-time through PIM. A standing assignment on an account nobody has signed into in six months is the single most common real-world finding here, and it is exactly the kind of thing an access-review spreadsheet tends to miss because the account still technically works.
- Guest users who ended up with more access than anyone intended, usually through nested group membership that nobody traced through by hand. A vendor or former contractor's guest account gets added to an "IT Admins" group to unblock one project, and two years later it is still there.
- Service principals and app registrations with ownership or credential-reset rights over other apps. Each one of those is a live credential-theft path: compromise the app with the weakest secret handling, and you inherit control of whatever it owns.
- MFA state on privileged accounts, when your collected data includes it. Disabled MFA on an account that also holds a privileged role gets flagged directly.
None of that requires a dead tenant to be real. It requires an environment that has been in use for more than about a year, which is roughly how long it takes for one team offboarding, one urgent access grant nobody circled back on, and one forgotten test account to produce the same shape of finding I got, just with a lower number attached.
Who should actually use it, and who should not
BloodBash is a good fit in a few concrete situations: you are inheriting administration of a tenant or domain (new hire, new client, an acquisition) and want an honest picture of who actually holds power before you take responsibility for it; you are prepping for a SOC 2, ISO 27001, or similar audit where privileged-access review is a control, and would rather surface the finding yourself than have an auditor surface it for you; you are a consultant or MSP doing a scoped, time-boxed assessment and cannot justify standing up full BloodHound infrastructure for a single engagement; or you just want a repeatable quarterly check, same collector, same command, and a diff against last quarter's output to see what changed.
What it actually buys you in each of those cases is the same thing: turning "who has admin rights" from a manual exercise, clicking through the Entra portal or exporting a spreadsheet and eyeballing it, into a computed graph that traces the real path, including the nested-group and ownership chains a manual review reliably misses. That is the actual value, not the terminal output itself.
It is the wrong tool if you need Azure Resource Manager-level coverage (subscription, resource group, or resource role assignments) alongside your Entra ID picture, since none of that reaches the graph, as the letsencrypt and internal-automation app registrations in my own tenant proved. It is also not the right choice for anything your team will run repeatedly and share results from as a group: BloodHound Community Edition's web UI and persistent graph, or BloodHound Enterprise if you need continuous monitoring, are better suited to recurring use across a team. BloodBash's strength is a fast, disposable, single-analyst pass, not a shared, ongoing system of record.
How to run it without hitting the same walls
Check your tenant's license tier before you start. If it does not carry Entra ID Premium P1 or P2, plan on the users collection failing and budget time for the workaround above, rather than discovering it mid-run.
Reuse your Azure CLI session instead of creating an app registration. az account get-access-token piped into AzureHound's --jwt flag works and skips an unnecessary setup step.
Treat every finding involving a Microsoft first-party service principal as needing verification, not action. If a flagged app or service principal sounds like standard Microsoft plumbing (Media Service, SharePoint Online, Teams, Exchange), confirm it is not before you spend time on it.
Check Azure RBAC separately, every time. az role assignment list --subscription <id> takes seconds and covers ground this tool structurally cannot.
Run it on anything you own that has not been reviewed in a while, not just active production tenants. The forgotten tenant, in my case, was where the actual finding was. Live, well-managed environments tend to have already caught the obvious problems; the neglected ones are where privilege quietly piles up.
Frequently asked questions
Is BloodBash free?
Yes. It is open source (MIT licensed) and free to run. The only cost is your time collecting data with SharpHound or AzureHound first, both of which are also free and open source.
Do I need BloodHound installed to use BloodBash?
No. That is the entire point of the tool. It reads the same JSON output SharpHound and AzureHound produce, but analyzes it offline without a Neo4j database or the BloodHound web interface.
Why did AzureHound return zero users on my tenant?
Almost certainly the same Entra ID Premium licensing gate described above. AzureHound's default user query requests a sign-in activity field that Microsoft Graph rejects on tenants without a P1 or P2 license, regardless of your admin rights. Check your tenant's license SKUs, and if there is no premium license, query users directly through Microsoft Graph without that field as a workaround.
Does BloodBash cover Azure subscription-level permissions, like Owner or Contributor roles?
No. It only analyzes what SharpHound and AzureHound collect, which is Active Directory and Entra ID directory data. Azure Resource Manager role assignments (subscription, resource group, and resource-level RBAC) are a separate API and a separate check you need to run yourself.
Is it safe to run against a production tenant?
The collection step (SharpHound or AzureHound) is read-only and does not modify anything, so the risk is mainly about who else can see the output: a full export contains your real directory structure, admin roster, and role assignments, so treat the resulting JSON file like the sensitive document it is, and only run this against tenants you are authorized to assess.
What will I actually find if I run this against my own tenant?
Almost certainly not four forgotten Global Admins, unless you also have an abandoned tenant lying around. On an active environment, expect smaller but still real findings: standing (not just-in-time) privileged roles on accounts nobody uses often, guest users who inherited access through a group nobody double-checked, and service principals with ownership rights over other apps. Those three patterns account for most of what this class of tool turns up in practice.
How does BloodBash compare to just running BloodHound itself?
BloodHound gives you an interactive graph you can explore visually and is the better choice for a team that will come back to the data repeatedly. BloodBash trades that away for speed: no database, no UI, just a prioritized findings list in your terminal for a single, disposable pass.
The short version
BloodBash does what it says: point it at real collector data and it will tell you, fast and for free, who can reach Global Administrator and how, replacing a manual portal review with a computed graph that actually traces nested groups and ownership chains. The catch is that "real collector data" is not guaranteed by default. My tenant's license tier silently zeroed out the most important input, the tool flagged Microsoft's own infrastructure as a possible finding, and it structurally cannot see Azure subscription-level privilege at all. None of that makes it a bad tool. It makes it a fast first pass that still needs a second one: check your license tier before you trust an empty result, sanity-check anything involving Microsoft's own service principals, and run Azure RBAC checks by hand every time, because this tool never will. In my case, that combination surfaced four Global Administrators nobody had looked at in years. On an environment you actually use day to day, run the same combination and expect a smaller number, a stale standing role or an overreaching guest account, not zero.
IAM Best Practices Cheatsheet
Quick reference for securing identities across Entra ID, AWS IAM, and GCP.
No spam. Unsubscribe anytime.
Continue Learning
Identity & Access Management Roadmap
Identity is the new perimeter.
Microsoft Cloud Solution Architect
Cloud Solution Architect with deep expertise in Microsoft Azure and a strong background in systems and IT infrastructure. Passionate about cloud technologies, security best practices, and helping organizations modernize their infrastructure.
Share this article
Questions & Answers
Related Articles
Need Help with Your Security?
Our team of security experts can help you implement the strategies discussed in this article.
Contact Us