-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathLookForSPNWithSecrets.ps1
More file actions
125 lines (101 loc) · 4.96 KB
/
Copy pathLookForSPNWithSecrets.ps1
File metadata and controls
125 lines (101 loc) · 4.96 KB
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
<#
.SYNOPSIS
Reports all Service Principals in the tenant that have one or more secrets.
.DESCRIPTION
Queries Microsoft Graph for all Service Principals and filters those with
active passwordCredentials. Highlights foreign SPs (homed in another tenant)
as they represent the highest risk surface for the credential abuse attack
described at: https://dirkjanm.io/azure-ad-privilege-escalation-application-admin/
.REQUIREMENTS
- Microsoft.Graph PowerShell module
- Application.Read.All or Directory.Read.All
.NOTES
Author : Jan Bakker
Blog : https://janbakker.tech
#>
#Requires -Modules Microsoft.Graph.Authentication, Microsoft.Graph.Applications
[CmdletBinding()]
param(
[string]$TenantId,
# Flag foreign SPs (homed in a different tenant)
[switch]$HighlightForeign,
# Export results to CSV
[string]$ExportCsv
)
# ---------------------------------------------------------------
# 1. Connect
# ---------------------------------------------------------------
$connectParams = @{ Scopes = "Application.Read.All" }
if ($TenantId) { $connectParams.TenantId = $TenantId }
Connect-MgGraph @connectParams -NoWelcome
$tenantId = (Get-MgContext).TenantId
Write-Host "`n[+] Connected to tenant: $tenantId`n" -ForegroundColor Cyan
# ---------------------------------------------------------------
# 2. Fetch all SPs with passwordCredentials populated
# ---------------------------------------------------------------
Write-Host "[*] Fetching service principals..." -ForegroundColor Gray
$allSPs = Get-MgServicePrincipal -All `
-Property "id,appId,displayName,appOwnerOrganizationId,servicePrincipalType,passwordCredentials" |
Where-Object { $_.PasswordCredentials.Count -gt 0 }
Write-Host "[+] Found $($allSPs.Count) SP(s) with at least one secret.`n" -ForegroundColor Green
# ---------------------------------------------------------------
# 3. Build report
# ---------------------------------------------------------------
$now = Get-Date
$report = foreach ($sp in $allSPs) {
$isForeign = $sp.AppOwnerOrganizationId -and
$sp.AppOwnerOrganizationId -ne $tenantId
foreach ($cred in $sp.PasswordCredentials) {
$isExpired = $cred.EndDateTime -and ($cred.EndDateTime -lt $now)
$daysLeft = if ($cred.EndDateTime) {
[math]::Round(($cred.EndDateTime - $now).TotalDays)
} else { $null }
[PSCustomObject]@{
SPDisplayName = $sp.DisplayName
SPObjectId = $sp.Id
AppId = $sp.AppId
SPType = $sp.ServicePrincipalType
Hometenant = $sp.AppOwnerOrganizationId ?? "N/A (Managed Identity)"
IsForeignSP = $isForeign
SecretDisplayName = $cred.DisplayName ?? "(no name)"
KeyId = $cred.KeyId
Hint = $cred.Hint
SecretStart = $cred.StartDateTime
SecretExpiry = $cred.EndDateTime ?? "No expiry set"
DaysUntilExpiry = $daysLeft
IsExpired = $isExpired
}
}
}
# ---------------------------------------------------------------
# 4. Display results
# ---------------------------------------------------------------
if (-not $report) {
Write-Host "No service principals with secrets found." -ForegroundColor Yellow
return
}
# Summary table
$report | Sort-Object IsForeignSP -Descending |
Format-Table SPDisplayName, IsForeignSP, SecretDisplayName, SecretExpiry, IsExpired, DaysUntilExpiry `
-AutoSize
# Risk summary
$foreignWithSecrets = $report | Where-Object { $_.IsForeignSP } |
Select-Object -ExpandProperty SPDisplayName -Unique
$expiredSecrets = $report | Where-Object { $_.IsExpired }
$noExpiry = $report | Where-Object { $_.SecretExpiry -eq "No expiry set" }
Write-Host "`n--- Risk Summary ---" -ForegroundColor Yellow
Write-Host " Total SPs with secrets : $(($report | Select-Object SPObjectId -Unique).Count)"
Write-Host " Total secrets : $($report.Count)"
Write-Host " Foreign SPs with secrets: $($foreignWithSecrets.Count) → $($foreignWithSecrets -join ', ')" `
-ForegroundColor $(if ($foreignWithSecrets) { 'Red' } else { 'Green' })
Write-Host " Secrets with no expiry : $($noExpiry.Count)" `
-ForegroundColor $(if ($noExpiry) { 'Red' } else { 'Green' })
Write-Host " Expired secrets : $($expiredSecrets.Count)" `
-ForegroundColor $(if ($expiredSecrets) { 'Yellow' } else { 'Green' })
# ---------------------------------------------------------------
# 5. Optional CSV export
# ---------------------------------------------------------------
if ($ExportCsv) {
$report | Export-Csv -Path $ExportCsv -NoTypeInformation -Encoding UTF8
Write-Host "`n[+] Exported to $ExportCsv" -ForegroundColor Cyan
}