This PoC community project provides a sample PowerShell
script that collects Microsoft Entra ID permissions related
to Temporary Access Passes (TAPs)
and Passkeys (FIDO2 security keys or mobile devices)
and exports the data in BloodHound OpenGraph format.
TAPs and Passkeys can be registered by privileged malicious actors for other users. By authenticating with one of these methods afterwards, they can bypass MFA requirements and perform privilege elevation. This is in principle similar to the AZResetPassword edge, but with stronger requirements and more serious impact, but also more attractive to adversaries as it doesn’t result in the target user losing their ability to authenticate themselves with a password they know.
These authentication methods are disabled by default in the tenant, so they must be enabled first by a legitimate admin or the malicious actor, if they have the right permissions.
TAPs can easily be created for other users by using the Microsoft Entra admin center, Microsoft Graph API, or PowerShell:
New-MgUserAuthenticationTemporaryAccessPassMethod `
-UserId 'john.doe@contoso.com' `
-IsUsableOnce `
-LifetimeInMinutes 60 | Format-List
Sample output:
Id: 00aa00aa-bb11-cc22-dd33-44ee44ee44ee
CreatedDateTime: 5/22/2022 11:19:17 PM
IsUsable: True
IsUsableOnce: True
LifetimeInMinutes: 60
TemporaryAccessPass: TAPRocks!
However, a 3rd-party tool is required to perform administrative registration of Passkeys. One such utility is the DSInternals.Passkeys PowerShell module:
The following data is collected by the Get-EntraAuthenticationPolicyData.ps1 PowerShell script from an Entra ID tenant:
- UserAuthenticationMethod.ReadWrite.All
- UserAuthMethod-Passkey.ReadWrite.All
- Policy.ReadWrite.AuthenticationMethod
The following new nodes and edges are created based on the data collected:
This node represents the tenant-wide authentication method policy:
Only a subset of the available settings is ingested into BloodHound. The following boolean properties are currently configured on the AZAuthenticationPolicy node:
Property | Description |
---|---|
tapEnabled | Indicates whether the Temporary Access Pass authentication method is enabled in the tenant. |
tapIncludesAllUsers | Indicates whether all users are enabled to use Temporary Access Passess. |
passkeyEnabled | Indicates whether the Passkey authentication method is enabled in the tenant. |
passkeyIncludesAllUsers | Indicates whether all users are enabled to use Passkeys. |
The following new boolean properties are added to pre-existing AZUser nodes:
Property | Description |
---|---|
tapEnabled | Indicates whether the Temporary Access Pass authentication method is enabled for this user. |
passkeyEnabled | Indicates whether the Passkey authentication method is enabled for this user. |
These user properties are pre-calculated based on the AZTapInclude, AZTapExclude, AZPasskeyInclude, and AZPasskeyExclude edges.
Property | Value |
---|---|
Start node type | AZGroup |
End node type | AZAuthenticationPolicy |
Transitive | No |
Groups of users that are enabled to use the Temporary Access Pass authentication method.
Property | Value |
---|---|
Start node type | AZGroup |
End node type | AZAuthenticationPolicy |
Transitive | No |
Groups of users that are excluded from the Temporary Access Pass policy.
Property | Value |
---|---|
Start node type | AZGroup |
End node type | AZAuthenticationPolicy |
Transitive | No |
Groups of users that are enabled to use the Passkey authentication method.
Property | Value |
---|---|
Start node type | AZGroup |
End node type | AZAuthenticationPolicy |
Transitive | No |
Groups of users that are excluded from the Passkey policy.
graph LR
g1((AZGroup1)) == AZTapInclude ==> P(AZAuthenticationPolicy)
g2((AZGroup2)) == AZTapExclude ==> P
u1(AZUser1) -- AZMemberOf --> g1
g3((AZGroup3)) == AZPasskeyInclude ==> P(AZAuthenticationPolicy)
g4((AZGroup4)) == AZPasskeyExclude ==> P
u2(AZUser2) -- AZMemberOf --> g3
g5((AZGroup5)) -- AZMemberOf --> g4
u3(AZUser3) -- AZMemberOf --> g5
u3 -- AZMemberOf --> g3
P <-- 1:1 --> t{AZTenant}
Notice that passkeys cannot be registered for AZUser3 because of the AZPasskeyExclude transitive edge.
Property | Value |
---|---|
Start node type | AZServicePrincipal |
End node type | AZTenant |
Transitive | No |
This edge represents the tenant-wide Policy.ReadWrite.AuthenticationMethod application permission.
Property | Value |
---|---|
Start node type | AZServicePrincipal or AZRole |
End node type | AZAuthenticationPolicy |
Transitive | Yes |
Note
The current version of BloodHound does not support transitive edges in OpenGraph.
This edge indicates who is in control of the authentication method policies, i.e, service principals with the Policy.ReadWrite.AuthenticationMethod permission and the Global Administrator and Authentication Policy Administrator roles.
This diagram illustrates the possible relationships:
graph LR
a1(AZServicePrincipal1) == AZChangeAuthenticationPolicy ==> p((AZAuthenticationPolicy))
a1(AZServicePrincipal1) -- AZMGPolicy_ReadWrite_AuthenticationMethod --> t{AZTenant}
r1(Authentication Policy Administrator) == AZChangeAuthenticationPolicy ==> p
r2(Global Administrator) == AZChangeAuthenticationPolicy ==> p
p <-- 1:1 --> t
u1(AZUser1) -- AZOwns --> a1
u2(AZUser2) -- AZHasRole --> r1
u3(AZUser3) -- AZHasRole --> r2
a2(AZServicePrincipal2) -- AZHasRole --> r1
Note
The 1:1 edge between the authentication policy and tenant is not actually created by the script.
The relationship is only represented by the Tenant ID
property of the policy node.
Property | Value |
---|---|
Start node type | AZServicePrincipal |
End node type | AZTenant |
Transitive | No |
This edge represents the tenant-wide UserAuthenticationMethod.ReadWrite.All application permission.
Property | Value |
---|---|
Start node type | AZServicePrincipal |
End node type | AZTenant |
Transitive | No |
This edge represents the tenant-wide UserAuthMethod-Passkey.ReadWrite.All application permission.
Property | Value |
---|---|
Start node type | AZServicePrincipal or AZRole |
End node type | AZUser |
Transitive | Yes |
Note
The current version of BloodHound does not support transitive edges in OpenGraph.
This edge represents the permission to create new Temporary Access Passes for the target user. The edge is created based on the following conditions:
- The TAP method is enabled in the tenant-wide AZAuthenticationPolicy. AND
- The TAP policy applies to the target AZUser. AND
- The source AZServicePrincipal has the UserAuthenticationMethod.ReadWrite.All application permission. OR
- The source AZRole is Global Administrator. OR
- The source AZRole is Privileged Authentication Administrator.
Warning
The Authentication Administrator role and administrative units are not yet supported by the tool.
Property | Value |
---|---|
Start node type | AZServicePrincipal or AZRole |
End node type | AZUser |
Transitive | Yes |
Note
The current version of BloodHound does not support transitive edges in OpenGraph.
This edge represents the permission to register new Passkeys on behalf of the target user. The edge is created based on the following conditions:
- The Passkey method is enabled in the tenant-wide AZAuthenticationPolicy. AND
- The Passkey policy applies to the target AZUser. AND
- The source AZServicePrincipal has the UserAuthenticationMethod.ReadWrite.All application permission. OR
- The source AZServicePrincipal has the UserAuthMethod-Passkey.ReadWrite.All application permission. OR
- The source AZRole is Global Administrator. OR
- The source AZRole is Privileged Authentication Administrator.
Warning
The Authentication Administrator role and administrative units are not yet supported by the tool.
graph LR
u1(AZUser1)
u2(AZUser2)
u3(AZUser3)
u4(AZUser4)
u5(AZUser5)
a1(AZServicePrincipal1)
a2(AZServicePrincipal2)
a3(AZServicePrincipal3)
r1(Privileged Authentication Administrator)
t{AZTenant}
a1 -- AZMGUserAuthenticationMethod_ReadWrite_All --> t
a2 -- AZMGUserAuthenticationMethod_Passkey_ReadWrite_All --> t
a3 -- AZHasRole --> r1
r1 == AZRegisterPasskey ==> u5
r1 == AZCreateTAP ==> u5
a1 == AZCreateTAP ==> u5
a1 == AZRegisterPasskey ==> u5
a2 == AZRegisterPasskey ==> u5
u4 -- AZHasRole --> r1
u1 -- AZOwns --> a1
u2 -- AZOwns --> a2
u3 -- AZMGAddSecret --> a3
u5 -- AZGlobalAdmin --> t
The Get-EntraAuthenticationPolicyData.ps1 PowerShell script reads authentication method policies and service principal permissions. It therefore requires the following Microsoft Graph delegated permissions (OAuth scopes):
The user executing the script must be assigned at least the Directory Readers role.
- Ingest the base Entra ID data using AzureHound.
- Run the Get-EntraAuthenticationPolicyData.ps1 script to generate a BloodHound OpenGraph JSON file.
- Upload the generated
AuthenticationPolicyData_*.json
file to BloodHound. - Optionally register the
AZAuthenticationPolicy
custom node type by uploading the CustomNodes.json file using the BloodHound API. - Try running the sample queries or your own ones.
File | Description |
---|---|
Get-EntraAuthenticationPolicyData.ps1 | Main script that collects the data. |
BloodHound.OpenGraph.Model.psm1 | Helper PowerShell module implementing the BloodHound OpenGraph data model. |
BloodHound.OpenGraph.Model.Tests.ps1 | Simple Pester test cases for the data model. |
AuthenticationPolicyData_Sample.json | Sample file generated by the Get-EntraAuthenticationPolicyData.ps1 script. |
bloodhound-opengraph.schema.json | A JSON schema file for BloodHound OpenGraph. |
CustomNodes.json | Icon and color definitions for the custom node types. |
This sections contains sample Cypher queries related to Entra ID authentication method policies.
Show the properties of the authentication policy node:
MATCH (n:AZAuthenticationPolicy) RETURN n
Show entities that are directly in control of the authentication policy:
MATCH p=(:AZBase)-[:AZChangeAuthenticationPolicy]->(:AZAuthenticationPolicy) RETURN p
Show entities that are indirectly in control of the authentication policy:
MATCH directControl=(:AZBase)-[:AZChangeAuthenticationPolicy]->(:AZAuthenticationPolicy)
MATCH indirectControl=(:AZBase)-[:AZ_ATTACK_PATHS]->(:AZBase)-[:AZChangeAuthenticationPolicy]->(:AZAuthenticationPolicy)
RETURN directControl,indirectControl
LIMIT 1000
Show the authentication method policy group inclusions and exclusions:
MATCH p=(:AZGroup)-[:AZTapInclude|AZTapExclude|AZPasskeyInclude|AZPasskeyExclude]->(:AZAuthenticationPolicy) RETURN p
Warning
This query may fail if no edge of a given kind, e.g., AZTapInclude, exists.
Show the authentication method policy user inclusions and exclusions, while considering nested group membership:
MATCH directAssignment=(:AZGroup)-[:AZTapInclude|AZTapExclude|AZPasskeyInclude|AZPasskeyExclude]->(:AZAuthenticationPolicy)
MATCH nestedMembership=(:AZBase)-[:AZMemberOf*1..]->(:AZGroup)-[:AZTapInclude|AZTapExclude|AZPasskeyInclude|AZPasskeyExclude]->(:AZAuthenticationPolicy)
RETURN directAssignment,nestedMembership
Show service principals that can register TAPs or passkeys on behalf of other users:
MATCH p=(:AZServicePrincipal)-[:AZMGUserAuthenticationMethod_ReadWrite_All|AZMGUserAuthenticationMethod_Passkey_ReadWrite_All]->(:AZTenant)
RETURN p
Show actors who can create TAPs or Passkeys for a specific user:
MATCH p=(:AZBase)-[:AZCreateTAP|AZRegisterPasskey]->(target:AZUser {name: 'ADELEV@LAB.DSINTERNALS.COM'})
RETURN p
Before uploading the JSON data to BloodHound CE backed by the Neo4j database, the following code snippet must be deleted first:
"metadata": {
"source_kind": "EntraTapPasskey"
},
Data import would otherwise fail with an error concerning duplicate nodes. This issue is not present when BloodHound is backed by the PostgreSQL database.
The Authentication Administrator role is not yet supported by the Get-EntraAuthenticationPolicyData.ps1 PowerShell script, as its logic is harder to implement. The following built-in roles are considered unprivileged by Entra and Authentication Administrators can change their authentication methods:
- Authentication Administrator
- Directory Readers
- Guest Inviter
- Message Center Reader
- Password Administrator
- Reports Reader
- User Experience Success Manager
- Usage Summary Reports Reader
If a user is a member of any other built-in or custom role or is a member or owner of a role-assignable group or has a role scoped to a restricted management administrative unit, then Authentication Administrators have no control over them, but Privileged Authentication Administrators still do. This behavior is similar to AZResetPassword.
Entra ID administrative units are not yet supported by BloodHound.
If the current authentication method policy prevents TAPs or Passkeys to be created for a user, but a malicious actor has the permissions to change the policy, they would be still be able to take over the target user's account. One such situation is illustrated on the following diagram:
graph TB
u1(AZUser1)
u2(AZUser2)
a(AZServicePrincipal)
r(Authentication Administrator)
g(AZGroup)
p(AZAuthenticationPolicy)
t{AZTenant}
u2 -- AZMemberOf --> g
g -- AZTapExclude --> p
u1 == HasRole ==> r
u1 -- AZOwns --> a
a == AZChangeAuthenticationPolicy ==> p
a -- AZMGPolicy_ReadWrite_AuthenticationMethod --> t
u1 -. AZCreateTAP .-> u2
Such edge composition is not yet implemented in this tool, but could be discovered using a custom Cypher query.