private/tests/Test-Assessment.21885.ps1
<# .SYNOPSIS Checking App registrations must not have reply URLs containing *.azurewebsites.net #> function Test-Assessment-21885 { [CmdletBinding()] param($Database) Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose $activity = "Checking App registrations must not have reply URLs containing *.azurewebsites.net, URL shorteners, or localhost, wildcard domains" Write-ZtProgress -Activity $activity -Status "Getting policy" $sql = @" select id, appid, displayName, replyUrls, accountEnabled, appOwnerOrganizationId from main."ServicePrincipal" WHERE EXISTS ( SELECT 1 FROM UNNEST(replyUrls) AS t(item) WHERE item LIKE '%azurewebsites.net%' ) order by displayName "@ $results = Invoke-DatabaseQuery -Database $Database -Sql $sql $riskyAppsHigh = @() $riskyAppsMedium = @() $resolvedDomainsCache = @{} foreach ($item in $results) { $riskyUrlsHigh = @() $riskyUrlsMedium = @() foreach ($url in $item.replyUrls) { # skip localhost and non http(s) urls if ($url -like "*localhost*") { continue } if ($url -notlike "http*") { continue } try { # skip invalid urls $uri = [System.Uri]::new($url) } catch { continue } # Get domain from $uri $domain = $uri.Host if ($resolvedDomainsCache.ContainsKey($domain)) { $isDnsResolved = $resolvedDomainsCache[$domain] } else { Write-ZtProgress -Activity 'Checking domain' -Status $url # Cache domain resolution results to avoid multiple DNS queries $isDnsResolved = Test-DomainResolves -Domain $domain $resolvedDomainsCache[$domain] = $isDnsResolved } if ($isDnsResolved) { if ($url -like "*azurewebsites.net*") { $riskyUrlsMedium += $url } } else { # This is a high risk URL since dns doesn't resolve $riskyUrlsHigh += $url } } if ($riskyUrlsHigh.Count -gt 0) { $riskyAppsHigh += $item } if ($riskyUrlsMedium.Count -gt 0) { # It's okay to repeat so we show the right urls based on the risk level $riskyAppsMedium += $item } # Add the risky URLs as a property to the item $item | Add-Member -MemberType NoteProperty -Name "highRiskUrls" -Value $riskyUrlsHigh $item | Add-Member -MemberType NoteProperty -Name "mediumRiskUrls" -Value $riskyUrlsMedium } $passed = $riskyAppsHigh.Count -eq 0 -and $riskyAppsMedium.Count -eq 0 $riskLevel = 'Medium' if ($passed) { $testResultMarkdown += "No unsafe redirect URIs found`n`n%TestResult%" } else { $testResultMarkdown += "Unsafe redirect URIs found`n`n%TestResult%" if ($riskyAppsHigh.Count -gt 0) { $riskLevel = 'High' } } if ($riskyAppsHigh.Count -gt 0) { $testResultMarkdown += "`n## Apps with redirect URI domains that don't resolve `n`n" $testResultMarkdown += "`nThese apps can be hijacked by an attacker by registering this domain.`n`n" $testResultMarkdown += Get-RiskyAppList -Apps $riskyAppsHigh -Icon "❌" -ShowRiskLevel 'High' } if ($riskyAppsMedium.Count -gt 0) { $testResultMarkdown += "`n## Apps with unsafe redirect URIs `n`n" $testResultMarkdown += "`nThese apps can be hijacked by an attacker by registering this domain.`n`n" $testResultMarkdown += Get-RiskyAppList -Apps $riskyAppsMedium -Icon "⚠️" -ShowRiskLevel 'Medium' } $testResultMarkdown = $testResultMarkdown -replace "%TestResult%", $mdInfo Add-ZtTestResultDetail -TestId '21885' -Title "App registrations must not have reply URLs containing *.azurewebsites.net, URL shorteners, or localhost, wildcard domains" ` -UserImpact Low -Risk $riskLevel -ImplementationCost High ` -AppliesTo Identity -Tag Identity ` -Status $passed -Result $testResultMarkdown } function Get-RiskyAppList($Apps, $Icon, $ShowRiskLevel) { $mdInfo = "" $mdInfo += "| | Name | Invalid Redirect URIs | App owner tenant |`n" $mdInfo += "| :--- | :--- | :--- | :--- |`n" foreach ($item in $Apps) { $tenantName = Get-ZtTenantName -tenantId $item.appOwnerOrganizationId $tenantName = Get-SafeMarkdown $tenantName $portalLink = "https://entra.microsoft.com/#view/Microsoft_AAD_IAM/ManagedAppMenuBlade/~/Overview/objectId/$($item.id)/appId/$($item.appId)" $riskyReplyUrls = @() if ($ShowRiskLevel -eq 'High') { $riskyReplyUrls = $item.highRiskUrls } else { $riskyReplyUrls = $item.mediumRiskUrls } $riskyReplyUrls = $riskyReplyUrls | ForEach-Object { '`' + $_ + '`' } $riskyReplyUrls = $riskyReplyUrls -join ', ' $mdInfo += "| $($Icon) | [$(Get-SafeMarkdown($item.displayName))]($portalLink) | $riskyReplyUrls | $tenantName |`n" } return $mdInfo } |