AzureRedOps: A Practitioner's Toolkit for Assessing Entra ID Tenants

Microsoft Entra ID, which most of us still call Azure AD out of habit, is the identity control plane for the vast majority of enterprise cloud estates. It brokers authentication to Microsoft 365, the Azure Resource Manager, and the long tail of SaaS applications that organizations wire into their tenant over OAuth 2.0 and OpenID Connect. Identity has become the perimeter, so the security posture of a tenant comes down to how tokens get issued, which applications are trusted, and whether conditional access and multi-factor authentication are actually enforced everywhere they should be. AzureRedOps is a single-file Python toolkit that pulls together the authentication, enumeration, and post-exploitation capabilities a red teamer needs.

View the tool https://github.com/Mr-Un1k0d3r/AzureRedOps

The tool follows a simple model. Every operation is an activity you pick on the command line with the -a flag, and any access or refresh token you obtain during authentication can be cached locally in the .azure_creds file and reused later by name with -l. In practice that lets an operator move from capturing a credential, to directory reconnaissance over Microsoft Graph, to abusing whatever privileges that credential turns out to have, without copying raw JWTs back and forth between commands. It talks straight to the Entra ID endpoints under login.microsoftonline.com and to the Graph v1.0 and beta APIs, so you can see exactly which requests are going out. The -d and -dd switches expose that traffic at the request and response level, which matters when the goal is to reproduce a finding and then help the customer fix it.

Why Token-Centric Tooling Matters

A lot of assessment tooling treats authentication as a one-time gate: the password worked, move on. In a real tenant the interesting questions start after that gate. What audience and scope is a token good for? Can it be refreshed into a different first-party client? Does the directory let an ordinary user enumerate every other user, application, and service principal? AzureRedOps is built to answer those questions. The view activity decodes a cached JWT and surfaces the claims that matter, the refresh activity exchanges a refresh token for a fresh access token on either the v0 or the v2.0 endpoint, and gather-all sweeps the high-value Graph endpoints in one pass. The requests behind those activities are laid out below.

view                decode cached JWT, surface  exp (expiry) | tid (tenant) | scp (scope)

refresh  -v v0      POST https://login.microsoftonline.com/{tenant}/oauth2/token
                    body   grant_type=refresh_token
                           resource=https://graph.microsoft.com
                           refresh_token        (urlencoded)

refresh  -v v2.0    POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
                    body   grant_type=refresh_token, scope, refresh_token

gather-all          base https://graph.microsoft.com   (v1.0 unless noted, follows @odata.nextLink)
                      organization
                      policies/authorizationPolicy
                      beta/policies/authorizationPolicy/authorizationPolicy
                      policies/featureRolloutPolicies
                      policies/conditionalAccessPolicies
                      users
                      groups
                      applications
                      oauth2PermissionGrants
                      servicePrincipals
                      directoryRoles

Selecting the refresh version with -v lets you reason about the real blast radius of a single captured credential instead of stopping at whether it was valid, since a token minted for one resource can often be traded for another. The gather-all sweep follows the @odata.nextLink paging automatically and will write each endpoint to its own JSON file when you pass -j. The authorization policy in particular often gives away quiet misconfigurations, such as a defaultUserRolePermissions block that lets non-admin users register applications, or allowedToReadOtherUsers left enabled. Those are the settings that quietly turn a low-privilege foothold into tenant-wide reconnaissance, and they are easy to miss if you are not looking for them. The -fl filter and -exp expand switches keep that volume of output readable.

Device Code Phishing

The OAuth 2.0 device authorization grant was built for input-constrained devices like smart TVs, where you log in on a second device by typing a short code into a known URL. That same flow happens to be one of the most effective phishing techniques against Entra ID, mostly because nothing about it looks wrong to the victim. The attacker requests a device code on behalf of a legitimate Microsoft first-party application, then hands the resulting user code to the target with some plausible story. The target visits the genuine microsoft.com/devicelogin page, signs in with their real credentials, and clears any MFA prompt, all of it on Microsoft's own infrastructure. The tokens come back to whoever started the request, which is the attacker.

The diagram traces the exchange end to end, and the request listing below shows the same steps as concrete calls. In step one the phish-start activity posts to the devicecode endpoint with the chosen client_id and resource, and Entra ID answers with a user_code, a device_code, and the verification URL. Step three is the only human part of the attack: the operator relays that short user_code to the victim with a pretext, often a fake meeting or document prompt. In step four the victim authenticates at the real microsoft.com/devicelogin page and satisfies MFA, which is why the flow defeats password-and-MFA controls without ever touching a phishing proxy. Steps five and six are the capture loop, polling the token endpoint with the device_code grant type every fifteen seconds and swallowing the authorization_pending error until the victim finishes.

phish-start    POST https://login.microsoftonline.com/{tenant}/oauth2/devicecode
               body     client_id, resource
               returns  user_code, device_code, verification_uri

(out of band)  operator delivers user_code to the victim
victim         signs in at https://microsoft.com/devicelogin and clears MFA

phish-capture  POST https://login.microsoftonline.com/{tenant}/oauth2/token   [polls every 15s]
               body     grant_type=urn:ietf:params:oauth:grant-type:device_code
                        code=device_code
               returns  access_token + refresh_token   (cached in .azure_creds)

Because the client and scope are operator-controlled, the captured token can be shaped to the goal. The default client is the Microsoft Office application d3590ed6-52b3-4102-aeff-aad2292ab01c, and the phish-capture activity lets you resume polling against a device_code generated elsewhere with the -c flag. The hint baked into the tool is to request the scope https://graph.microsoft.com/.default offline_access openid so the returned token carries a refresh token usable for longer-lived Graph access. The defensive takeaway is specific. Restrict the device code flow with conditional access wherever it is not actually needed, and treat unusual device-code sign-ins as a strong detection signal.

Third-Party Application Consent and the OAuth Trust Problem

Every OAuth application registered in or consented to inside a tenant is a standing trust relationship. When a user, or an administrator acting for all users, grants an application access to resources, that consent gets recorded as a permission grant and sticks around until someone revokes it. Attackers work this from two angles. They register or control an application and talk a user into consenting to it, which gives them durable access that outlives a password reset because it rides on a refresh token rather than a credential. Or they go looking for applications already sitting in the tenant whose configuration makes them exploitable.

The figure shows how the auth-app activity drives the consent-driven side of that with a PKCE-protected authorization code flow, and the listing below gives the exact parameters. In step one the tool generates a PKCE pair by taking 32 random bytes as the verifier and the base64url-encoded SHA-256 of that value as the S256 challenge. Steps two and three present an authorization URL against the common authorize endpoint, requesting an offline_access scope, which the operator opens in a real browser. Step four is where the user consents and clears MFA. In step five Entra ID issues a 302 redirect that carries the authorization code back to the local HTTPS listener the tool stood up on localhost port 2342, serving the /getAuth endpoint from the bundled cert.pem and key.pem. Steps six and seven complete the exchange: the code and the original code_verifier are posted to the token endpoint, and Entra ID returns the access and refresh tokens.

auth-app  1  PKCE     verifier  = 32 random bytes
                      challenge = base64url(SHA256(verifier)), method = S256

          2  GET      https://login.microsoftonline.com/common/oauth2/v2.0/authorize
                      client_id, response_type=code, response_mode=query
                      redirect_uri=https://localhost:2342/getAuth
                      scope=openid profile offline_access https://graph.microsoft.com/.default
                      code_challenge, code_challenge_method=S256

          3  user consents + MFA
          4  302      redirect to https://localhost:2342/getAuth?code=...

          5  POST     https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
                      grant_type=authorization_code, code, code_verifier, redirect_uri
                      returns  access_token + refresh_token

magic-app    GET  .../v1.0/oauth2PermissionGrants         keep consentType == AllPrincipals
             GET  .../v1.0/servicePrincipals/{id}         keep appRoleAssignmentRequired == false
             GET  .../v1.0/applications(appId='{appId}')  flag oauth2PermissionScopes[0].type == User
                                                          and publicClient.redirectUris present

The discovery angle is handled by the magic-app activity, shown at the bottom of the listing. It enumerates the OAuth2 permission grants, isolates the ones consented for all principals in the tenant, and for each keeps the service principal where role assignment is not required. It then reads the matching application registration and flags it when the first published scope has a type of User and the registration exposes a public client with redirect URIs. That combination is what lets an attacker pull tokens through a public client without any per-user authorization, and the tool prints the display name, app ID, and every redirect URI it finds. Putting those apps in front of the blue team turns a vague worry about OAuth risk into a short, concrete list of trust relationships worth reviewing.

Password Spraying Against Applications Without MFA

Multi-factor authentication is the single best control against credential attacks, but it only protects what it actually covers. Plenty of tenants enforce MFA unevenly across the catalog of first-party Microsoft applications. Legacy authentication endpoints and some client IDs do not always trigger the same conditional access policies that guard the interactive web sign-in, and those gaps are exploitable. Password spraying is the technique that finds them. You try one or a few common passwords across many accounts, or in this case across many application identities, while staying under the lockout thresholds that would otherwise give you away.

As the diagram lays out, a single credential is fed into the spray engine and fanned out across many first-party client IDs rather than across many accounts, which keeps the attempt count per user low. The requests below show both halves of that fan-out. The v0 set submits a urlencoded resource-owner password request with the graph resource, while the v2.0 set posts a scope-based request, with a one second pause between attempts to stay quiet. The two branches on the right of the diagram are the whole point of the technique: the same password is rejected at App A, where MFA or conditional access intercepts the sign-in, yet accepted at App B, a client identity that has no MFA gate in front of it. When a login lands and you pass -cp, the tool decodes the returned token, prints its scp scope, and immediately probes the directory with the two top=1 reads shown, turning a successful login into an instant read on what that access is worth.

spray  v0 set     POST https://login.microsoftonline.com/{tenant}/oauth2/token   (urlencoded)
                  body  grant_type=password, username, password
                        resource=https://graph.microsoft.com

spray  v2.0 set   POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
                  body  grant_type=password, username, password, scope

                  client_ids from includes/auth_apps.json | 1 second delay per attempt

-cp on success    GET https://graph.microsoft.com/v1.0/users?$top=1
                  GET https://graph.microsoft.com/v1.0/applications?$top=1

spray-refresh     replay refresh_token across the same v0 + v2.0 client_id sets

The companion spray-refresh activity replays a captured refresh token across the same set of application IDs and maps which first-party clients that single token can be exchanged into. This is the clearest way to expose the case where a credential gets blocked at one application and accepted without complaint at another, which is exactly the failure mode that inconsistent MFA enforcement produces. The list-interest, interest, and knownids activities round this out by printing the categorized and known Microsoft application IDs the tool ships with, so you can pick sensible targets before you ever send a request.

From Findings to Remediation

What makes a tool like this worthwhile for defenders is that each offensive capability lines up with a control. Device code phishing makes the case for conditional access restrictions on the device code flow and for teaching users to be suspicious of unexpected device codes. The consent activities make the case for restricting user consent, requiring an admin approval workflow, and auditing permission grants and risky application configurations on a regular basis. The spraying activities make the case for enforcing MFA uniformly across every authentication surface, disabling legacy authentication, and alerting on bursts of failed sign-ins spread across applications. Because AzureRedOps issues plain, observable requests against documented endpoints, you can hand the resulting traffic to a detection team and confirm that the alerts you expect are really firing.

Where to Get AzureRedOps

AzureRedOps is open source and available on GitHub. You can find the project, the full source, on GitHub at https://github.com/Mr-Un1k0d3r/AzureRedOps. The repository is a compact Python 3 codebase an includes directory that holds the local PKCE web server, the curated application ID lists in apps.json and auth_apps.json, and the TLS material for the consent listener. Getting started is the usual pattern. Clone the repository, create a virtual environment, install the dependencies with pip install -r requirements.txt. The browser-driven activities also need the Playwright browser installed on the system.

The three attack vectors above are not the whole toolkit, only the most eye-catching parts of it. AzureRedOps also includes an auth-interactive activity that drives a real Chromium browser through Playwright and harvests every token from the recorded session, which is handy when conditional access or federated sign-in defeats a scripted flow. On the post-exploitation side it can register a new application with a client secret through register-app, create a security group with new-group, assign a directory role such as Global Administrator with add-group, invite an external guest user with invite, and upload a file to a victim's OneDrive with push-file. For reconnaissance it offers self, email, and permission for quick checks against the signed-in identity, and raw-url for issuing arbitrary authenticated Graph or REST requests with automatic paging. Those features are worth exploring once the initial access primitives have done their job.

Identity Is the Prime Target

Azure and Entra ID have become a prime attack vector precisely because so much trust is concentrated in one identity plane. A single captured token can reach mail, files, the directory, and the resource manager, and unlike a stolen password it is bound to a session rather than a secret, so it survives the password reset that defenders reach for first. That concentration is exactly why techniques like device code phishing, illicit application consent, and spraying against clients without MFA are so productive, and why an assessment tool that exercises all three from one place is useful for proving the risk and then driving it down.

The threat is not theoretical or limited to bespoke red teams. Commodity phishing kits have steadily moved up the stack from simple credential capture to adversary-in-the-middle proxies that defeat MFA, and the device code grant is now a supported option in several of those kits because it needs no malicious infrastructure and lands tokens on Microsoft's own login pages. Defenders should assume that device code abuse is within reach of ordinary phishing operators, not just skilled adversaries. Tightening conditional access around the device code and authentication flows, restricting user consent, and enforcing MFA evenly across every client identity are the controls that take the easy wins off the table. AzureRedOps, used under proper authorization, is a fast way to find out which of those wins are still sitting open in a tenant before someone less friendly does.