Every identity team that has driven a multi-factor authentication rollout knows the moment: leadership asks "are we done yet?", the Entra portal shows thousands of accounts, and the only way to answer the question in the GUI is to scroll, filter, and squint. The SANS Internet Storm Center diary this week walked through exactly that problem — using PowerShell and the Microsoft.Graph.Beta module against Microsoft Entra ID to programmatically enumerate which users are (and are not) registered for MFA, rather than clicking through the web interface.
This is unglamorous work, but it is where breaches live. In my incident response caseload, the initial access vector in a disproportionate number of business email compromise and cloud account takeover cases is a single account that "slipped through" the MFA rollout — a service account nobody owned, a test mailbox, a contractor whose account outlived the engagement, or an executive who got a permanent exception. Attackers don't defeat MFA; they route around it through the accounts you forgot.
Two things make this worth your time as a defender right now:
- The same enumeration that helps you close gaps helps an attacker find them. A threat actor with a foothold and a token (or a phished session cookie replayed through tools like TokenTactics or ROADrecon-style Graph abuse) will absolutely query user registration details to find accounts without MFA for password-spray or legacy-auth targeting. Your audit script and their reconnaissance script look nearly identical at the API layer.
- Microsoft continues to tighten MFA enforcement across Entra and Azure management planes, and conditional access gaps — legacy authentication, excluded groups, stale break-glass accounts — are actively exploited for initial access. If you cannot enumerate your residual non-MFA population on demand, you do not know your real attack surface.
There is no CVE here. This is a posture problem, and it is entirely fixable with scripting you can run today.
Technical Analysis: How the Enumeration Works
Affected scope
- Platform: Microsoft Entra ID (formerly Azure AD) tenants, all license tiers
- Tooling: PowerShell 5.1 / 7.x with
Microsoft.GraphandMicrosoft.Graph.Betamodules - API surface: Microsoft Graph
/betaendpoint, specifically the authentication methods reporting APIs
The audit method
The article's approach (and the one we use in client engagements) leans on the beta reporting endpoint that exposes per-user authentication method registration detail. The beta module exposes Get-MgBetaReportAuthenticationMethodUserRegistrationDetail, which returns objects with properties including UserPrincipalName, IsMfaRegistered, IsMfaCapable, and MethodsRegistered. Filtering for IsMfaRegistered eq false gives you the precise residual population — no portal scrolling required.
Required Graph permission scope: UserAuthenticationMethod.Read.All (and typically AuditLog.Read.All / Reports.Read.All depending on the report endpoint used), granted to the app registration or delegated context running the script.
Why the "beta" matters
The beta Graph endpoint is where Microsoft ships new identity reporting capabilities first — richer MFA registration detail, newer method types (passkeys, Temporary Access Pass), and properties that haven't been promoted to v1.0. The tradeoff, as the diary noted, is that beta contracts can change without notice, so operational scripts against beta endpoints should be revalidated after module updates and wrapped with error handling.
The attacker's view of the same data
From a detection standpoint, treat bulk reads of authentication method registration data as a dual-use signal:
- Legitimate: your IAM team, scheduled compliance scripts, known service principals
- Hostile: post-compromise reconnaissance to identify MFA-less accounts for targeted password spray, or to identify which users have only SMS/phone methods registered (SIM-swap or MFA-fatigue candidates)
A threat actor holding a compromised session will typically enumerate via Connect-MgGraph from an interactive PowerShell session, or hit https://graph.microsoft.com/beta/reports/authenticationMethods/userRegistrationDetails directly with a stolen token. Both paths are observable if you are collecting the right telemetry: Office 365 / Entra audit logs for Graph sign-ins, SigninLogs for first-time application usage, and endpoint process telemetry for the PowerShell execution itself.
Detection & Response
The detection strategy here is twofold: (1) catch unauthorized execution of Graph enumeration tooling on endpoints, and (2) hunt the identities that represent your actual MFA gap — accounts still authenticating with single-factor methods.
Sigma
The first rule fires on interactive PowerShell loading the Microsoft Graph SDK with authentication-method or registration-detail cmdlets — the signature of both the legitimate audit and attacker reconnaissance, so scope false positives by user and host. The second targets suspicious Graph SDK sign-in context visible in endpoint telemetry where the module is invoked from non-standard locations or by accounts outside the IAM admin group.
---
title: Microsoft Graph PowerShell Authentication Method Enumeration
description: Detects PowerShell execution loading Microsoft.Graph modules and invoking cmdlets that enumerate user MFA/authentication method registration detail. Dual-use activity — legitimate for IAM audits, also used by threat actors for reconnaissance of MFA gaps after initial access.
id: 3f8c2a71-9d4b-4e6a-b1c7-2a5d9f0e8c34
status: experimental
references:
- https://isc.sans.edu/diary/rss/33272
- https://attack.mitre.org/techniques/T1087/004/
author: Security Arsenal
date: 2026/02/13
tags:
- attack.discovery
- attack.t1087.004
- attack.t1059.001
logsource:
category: process_creation
product: windows
detection:
selection_shell:
Image|endswith:
- '\powershell.exe'
- '\pwsh.exe'
selection_cmdlets:
CommandLine|contains:
- 'Get-MgBetaReportAuthenticationMethodUserRegistrationDetail'
- 'Get-MgReportAuthenticationMethodUserRegistrationDetail'
- 'userRegistrationDetails'
- 'Get-MgUserAuthenticationMethod'
- 'Get-MgBetaUserAuthenticationMethod'
condition: selection_shell and selection_cmdlets
falsepositives:
- IAM and compliance teams running scheduled MFA enrollment audits
- Known automation service principals executing from designated admin hosts
level: medium
---
title: Microsoft Graph SDK Connection from Non-Admin Context
description: Detects Connect-MgGraph / Connect-MgBeta execution by users on workstations rather than designated admin or automation hosts. Graph SDK connections from general user endpoints are a strong post-compromise indicator when they request identity or authentication-method scopes.
id: 8b1e4f62-5c3a-47d9-a2e6-6f1b3c8d9047
status: experimental
references:
- https://isc.sans.edu/diary/rss/33272
- https://attack.mitre.org/techniques/T1550/
author: Security Arsenal
date: 2026/02/13
tags:
- attack.initial_access
- attack.t1550.001
- attack.t1059.001
logsource:
category: process_creation
product: windows
detection:
selection_shell:
Image|endswith:
- '\powershell.exe'
- '\pwsh.exe'
selection_connect:
CommandLine|contains:
- 'Connect-MgGraph'
selection_scopes:
CommandLine|contains:
- 'UserAuthenticationMethod.Read'
- 'User.Read.All'
- 'Directory.Read.All'
- 'Reports.Read.All'
- 'AuditLog.Read.All'
condition: selection_shell and selection_connect and selection_scopes
falsepositives:
- Helpdesk or IAM staff connecting interactively from admin workstations — maintain an allowlist of admin hosts and service accounts
level: high
KQL — Microsoft Sentinel / Defender
The first query hunts for accounts in your tenant still completing sign-ins without MFA — this is the operational output the audit script gives you, derived from sign-in telemetry so you can watch it continuously instead of running a point-in-time script. The second hunts for first-seen or rare use of the Microsoft Graph PowerShell enterprise application in sign-in logs, which is how interactive attacker-driven Graph enumeration typically appears.
// Query 1: Successful interactive sign-ins completed WITHOUT MFA (last 14 days)
// Tune the excluded service accounts to your environment's approved exceptions
SigninLogs
| where TimeGenerated > ago(14d)
| where ResultType == 0
| where AuthenticationRequirement == "singleFactorAuthentication"
| where AppDisplayName !in~ ("Azure Portal") // optional tuning
| where UserPrincipalName !startswith "svc-" // review service accounts separately
| summarize LastSingleFactorSignin = max(TimeGenerated),
AppsUsed = make_set(AppDisplayName, 10),
IPs = make_set(IPAddress, 10),
SigninCount = count()
by UserPrincipalName, UserId
| sort by SigninCount desc
// Query 2: Rare or first-time Microsoft Graph PowerShell sign-ins (hunt for attacker-driven Graph enumeration)
let KnownGraphUsers = SigninLogs
| where TimeGenerated between (ago(90d) .. ago(7d))
| where AppDisplayName has_any ("Microsoft Graph PowerShell", "Microsoft Graph Command Line Tools")
| distinct UserPrincipalName;
SigninLogs
| where TimeGenerated > ago(7d)
| where AppDisplayName has_any ("Microsoft Graph PowerShell", "Microsoft Graph Command Line Tools")
| where UserPrincipalName !in (KnownGraphUsers)
| project TimeGenerated, UserPrincipalName, AppDisplayName, IPAddress, ResultType,
AuthenticationRequirement, ConditionalAccessStatus, Location, UserAgent
| sort by TimeGenerated desc
Velociraptor VQL
Hunt endpoints for interactive PowerShell sessions that loaded the Microsoft Graph assemblies — useful for scoping whether an attacker's Graph reconnaissance touched a specific host during IR triage.
-- Hunt for PowerShell processes with Microsoft Graph module usage in command line
SELECT Pid, Name, CommandLine, Exe, Username, CreateTime
FROM pslist()
WHERE Name =~ '(?i)powershell|pwsh'
AND CommandLine =~ '(?i)MgGraph|MgBeta|userRegistrationDetails|AuthenticationMethod'
Audit Script: Enumerate the Residual Non-MFA Population
This is the defensive counterpart to the diary's method — a repeatable script that produces the "who is left" list, flags high-risk subsets (admins and accounts with only phishable methods), and exports for ticketing. Run it from a secured admin workstation under a dedicated service principal with least-privilege Graph scopes.
# Requires: Install-Module Microsoft.Graph.Beta -Scope CurrentUser
# Connect with least-privilege scopes for reporting only
Connect-MgGraph -Scopes "UserAuthenticationMethod.Read.All","Reports.Read.All","Directory.Read.All" -NoWelcome
# Pull full registration detail and filter to users NOT registered for MFA
$all = Get-MgBetaReportAuthenticationMethodUserRegistrationDetail -All
$notRegistered = $all | Where-Object { -not $_.IsMfaRegistered }
# Break out the dangerous subset: capable-of-nothing vs. registered-but-not-MFA
$report = $notRegistered | Select-Object UserPrincipalName, Id,
IsMfaRegistered, IsMfaCapable,
@{N='MethodsRegistered';E={$_.MethodsRegistered -join ';'}}
$report | Export-Csv -Path ".\MFA_Gap_Audit_$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation
# High-risk callouts: anyone with only phishable/single methods registered
$weakOnly = $all | Where-Object {
$_.IsMfaRegistered -and
($_.MethodsRegistered -notcontains 'microsoftAuthenticatorPush') -and
($_.MethodsRegistered -notcontains 'windowsHelloForBusiness') -and
($_.MethodsRegistered -notcontains 'fido2') -and
($_.MethodsRegistered -notcontains 'passKey')
}
$weakOnly | Select-Object UserPrincipalName, @{N='Methods';E={$_.MethodsRegistered -join ';'}} |
Export-Csv -Path ".\MFA_WeakMethodsOnly_$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation
Write-Host "Total users audited: $($all.Count)"
Write-Host "NOT MFA-registered: $($notRegistered.Count)"
Write-Host "MFA-registered but weak methods only: $($weakOnly.Count)"
Disconnect-MgGraph
Treat the CSV output as sensitive — it is literally a targeting list of your weakest accounts. Store it in a restricted location and delete it after remediation tickets are opened.
Remediation
Closing the gap is a process, not a script, but here is the sequence that works in real tenants:
- Establish the baseline. Run the audit above on a schedule (weekly during an active rollout). Export results to your ticketing system; assign each unenrolled account an owner and a due date. Accounts with no owner after two cycles are deprovisioning candidates.
- Enforce with Conditional Access, not encouragement. Require MFA for all users via a Conditional Access policy, staged through report-only mode first. Use the audit output as your exception backlog — every exclusion group member should have a documented reason and an expiry date.
- Kill legacy authentication. Legacy protocols (IMAP, POP, SMTP AUTH, older Office clients) bypass Conditional Access entirely and are the standard workaround attackers use against MFA-less accounts. Block legacy auth tenant-wide via Conditional Access; hunt residual legacy protocol sign-ins in SigninLogs (
ClientAppUsedfield) before and after the block. - Handle service accounts deliberately. Service accounts cannot do interactive MFA. Migrate them to managed identities or workload identity federation where possible; where not possible, restrict them with Conditional Access location policies (named locations = your datacenter egress IPs) and monitor them as high-value targets.
- Protect break-glass accounts asymmetrically. Break-glass accounts are commonly excluded from MFA policies by design — which makes them prime targets. Use FIDO2 or certificate-based auth for them, alert on any sign-in, and audit their exclusion group membership monthly.
- Watch for the enumeration itself. Deploy the Sigma and KQL detections above. Any interactive Graph PowerShell session requesting
UserAuthenticationMethod.Readscopes from a non-IAM host or user should page a human. - Re-audit after module updates. Because the reporting cmdlets used here live on the Graph
/betasurface, validate the script after anyMicrosoft.Graph.Betamodule upgrade — beta property names and filter behavior can change.
There is no vendor patch for this — the "vulnerability" is the delta between your MFA policy intent and your actual enrollment state. The audit script is the measurement instrument; Conditional Access is the fix; and detection on Graph enumeration is how you know when someone else is measuring your gaps too.
Related Resources
Security Arsenal Alert Triage Automation AlertMonitor Platform Book a SOC Assessment platform Intel Hub
Is your security operations ready?
Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.