private/tests-shared/Get-ZtAppWithUnsafeRedirectUris.ps1
<# .SYNOPSIS Checking App registrations must use safe redirect URIs #> function Get-ZtAppWithUnsafeRedirectUris { [CmdletBinding()] param( $Database, [ValidateSet('ServicePrincipal', 'Application')] [string] $Type, # Switch to run DNS resolution checks only [switch] $DnsCheckOnly ) #region Utility Functions function Get-ZtiRiskyAppList { [CmdletBinding()] param ( $Apps, $Type ) $mdInfo = "" $mdInfo += "| | Name | Unsafe Redirect URIs |" # Only add the app owner tenant column for ServicePrincipal if ($Type -eq 'ServicePrincipal') { $mdInfo += "App owner tenant |`n" } else { $mdInfo += "`n" } $mdInfo += "| :--- | :--- | :--- |" if ($Type -eq 'ServicePrincipal') { $mdInfo += " :--- |`n" } else { $mdInfo += "`n" } foreach ($item in $Apps) { if ($Type -eq 'ServicePrincipal') { $tenantName = Get-ZtTenantName -TenantId $item.appOwnerOrganizationId $tenantName = Get-SafeMarkdown -Text $tenantName $portalLink = "https://entra.microsoft.com/#view/Microsoft_AAD_IAM/ManagedAppMenuBlade/~/Overview/objectId/$($item.id)/appId/$($item.appId)" } else { $portalLink = "https://entra.microsoft.com/#view/Microsoft_AAD_IAM/ManagedAppMenuBlade/~/SignOn/objectId/$($item.id)/appId/$($item.appId)/preferredSingleSignOnMode/saml/servicePrincipalType/Application/fromNav/" } $riskyReplyUrls = $item.riskyUrls | ForEach-Object { '`' + $_ + '`' } $riskyReplyUrls = $riskyReplyUrls -join ', ' $mdInfo += "| $($Icon) | [$(Get-SafeMarkdown -Text $item.displayName)]($portalLink) | $riskyReplyUrls | $tenantName |`n" } $mdInfo } #endregion Utility Functions # Get current tenantid $currentTenantId = (Get-MgContext).TenantId $filter = "appOwnerOrganizationId = '$currentTenantId'" if ($Type -eq 'ServicePrincipal') { $filter = "appOwnerOrganizationId <> '$currentTenantId'" } $sql = @" select id, appid, displayName, replyUrls, accountEnabled, appOwnerOrganizationId from main."ServicePrincipal" WHERE $filter order by displayName "@ $results = Invoke-DatabaseQuery -Database $Database -Sql $sql $resolvedDomainsCache = @{} $tenantsToIgnore = @( 'f8cdef31-a31e-4b4a-93e4-5f571e91255a', # Microsoft Services '33e01921-4d64-4f8c-a055-5bdaffd5e33d' # AME ) #region Check for all apps all redirect urls $riskyApps = foreach ($item in $results) { $riskyUrls = @() if ($tenantsToIgnore -contains $item.appOwnerOrganizationId) { continue } foreach ($url in $item.replyUrls) { # skip localhost urls if ($url -like "*localhost*") { continue } if (-not $DnsCheckOnly) { if ($url -like "http:*") { #Should use https $riskyUrls += "1️⃣ $url" continue } if ($url -like "*azurewebsites.net*") { $riskyUrls += "2️⃣ $url" continue } # # Check if the url redirects to a different domain : # NOTE: This has been taken out of the spec for now. # $safeRedirect = Test-UriRedirectsToSameDomain -Url $url # if (!$safeRedirect) { # $riskyUrls += "6️⃣ $url" # } continue } try { # skip invalid urls $uri = [System.Uri]::new($url) } catch { continue } # Skip non http(s) urls if ($uri.Scheme -ne 'http' -and $uri.Scheme -ne 'https') { continue } # Get domain from $uri $domain = $uri.Host Write-ZtProgress -Activity 'Checking redirect uri' -Status $url if ($resolvedDomainsCache.ContainsKey($domain)) { $isDnsResolved = $resolvedDomainsCache[$domain] } else { # Cache domain resolution results to avoid multiple DNS queries $isDnsResolved = Test-DnsName -Name $domain $resolvedDomainsCache[$domain] = $isDnsResolved } if (-not $isDnsResolved) { $riskyUrls += "$url" } } if ($riskyUrls.Count -gt 0) { $item } # Add the risky URLs as a property to the item [PSFramework.Object.ObjectHost]::AddNoteProperty($item, 'riskyUrls', @($riskyUrls), $true) } #endregion Check for all apps all redirect urls $passed = @($riskyApps).Count -eq 0 if ($passed) { $testResultMarkdown += "No unsafe redirect URIs found" } else { $testResultMarkdown += "Unsafe redirect URIs found`n`n" $testResultMarkdown += "1️⃣ → Use of http(s) instead of https, 2️⃣ → Use of *.azurewebsites.net, 3️⃣ → Invalid URL, 4️⃣ → Domain not resolved`n`n" $testResultMarkdown += Get-ZtiRiskyAppList -Apps $riskyApps -Type $Type } # create and return a pscustomobject with testResultMarkdown and passed [PSCustomObject]@{ TestResultMarkdown = $testResultMarkdown Passed = $passed } } |