Adversaries may attempt to redirect users to malicious external domains through improperly configured redirect URIs in Entra ID application registrations to exfiltrate credentials or deploy phishing schemes. SOC teams should proactively hunt for this behavior in Azure Sentinel to identify potential credential theft or phishing attempts leveraging misconfigured applications.
KQL Query
let starttime = todatetime('{{StartTimeISO}}');
let endtime = todatetime('{{EndTimeISO}}');
let trustedPrefixes = dynamic([
"https://login.microsoftonline.com",
"https://login.microsoft.com",
"https://login.windows.net",
"https://portal.azure.com",
"http://localhost",
"https://localhost",
"http://127.0.0.1",
"https://127.0.0.1",
"urn:ietf:wg",
"ms-appx-web://",
"msal"
]);
AuditLogs
| where TimeGenerated between (starttime .. endtime)
| where OperationName in~ ("Add application", "Update application")
| where Result =~ "success"
| extend ModProps = TargetResources[0].modifiedProperties
| extend AppName = tostring(TargetResources[0].displayName)
| extend AppId = tostring(TargetResources[0].id)
| extend ActorUpn = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| extend ActorApp = tostring(parse_json(tostring(InitiatedBy.app)).displayName)
| extend ActorIp = iff(
isnotempty(tostring(parse_json(tostring(InitiatedBy.user)).ipAddress)),
tostring(parse_json(tostring(InitiatedBy.user)).ipAddress),
tostring(parse_json(tostring(InitiatedBy.app)).ipAddress))
| extend Actor = iff(isnotempty(ActorUpn), ActorUpn, ActorApp)
| mv-expand ModProp = ModProps
| where tostring(ModProp.displayName) =~ "ReplyUrls"
// newValue is a JSON array of redirect URIs
| extend RawUrls = tostring(ModProp.newValue)
| extend UrlArray = todynamic(RawUrls)
| mv-expand RedirectUri = UrlArray to typeof(string)
| extend RedirectUri = trim('"', trim(' ', RedirectUri))
| where isnotempty(RedirectUri)
// Exclude trusted prefixes
| where not(RedirectUri has_any (trustedPrefixes))
| extend AccountName = iff(ActorUpn has "@", tostring(split(ActorUpn, "@")[0]), Actor)
| extend AccountUPNSuffix = iff(ActorUpn has "@", tostring(split(ActorUpn, "@")[1]), "")
| project
TimeGenerated,
OperationName,
AppName,
AppId,
RedirectUri,
Actor,
AccountName,
AccountUPNSuffix,
ActorIp,
CorrelationId,
Result
| sort by TimeGenerated desc
id: c4e0baf0-283b-49d7-8b40-a1c72e92a4b2
name: Application registration or update with external redirect URI
description: |
Hunting query that identifies Entra ID application registrations and updates where one
or more redirect URIs (reply URLs) point to an external domain that is not a trusted
Microsoft endpoint, localhost, or a standard OAuth out-of-band value. An attacker who
can register or modify an application may add an attacker-controlled redirect URI to
intercept OAuth authorization codes and exchange them for access tokens without user
interaction after consent is granted.
Trusted prefixes excluded by this query include login.microsoftonline.com,
login.microsoft.com, login.windows.net, portal.azure.com, localhost, 127.0.0.1,
urn:ietf:wg, ms-appx-web, and msal. Any redirect URI not matching these prefixes
is surfaced for analyst review.
Analysts must validate every result. Benign matches include newly registered
third-party SaaS integrations, legitimate ISV applications, and authorized partner
applications that use their own domains as redirect targets.
References:
- https://learn.microsoft.com/azure/active-directory/develop/reply-url
- https://learn.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities
- https://attack.mitre.org/techniques/T1528/
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
tactics:
- CredentialAccess
relevantTechniques:
- T1528
query: |
let starttime = todatetime('{{StartTimeISO}}');
let endtime = todatetime('{{EndTimeISO}}');
let trustedPrefixes = dynamic([
"https://login.microsoftonline.com",
"https://login.microsoft.com",
"https://login.windows.net",
"https://portal.azure.com",
"http://localhost",
"https://localhost",
"http://127.0.0.1",
"https://127.0.0.1",
"urn:ietf:wg",
"ms-appx-web://",
"msal"
]);
AuditLogs
| where TimeGenerated between (starttime .. endtime)
| where OperationName in~ ("Add application", "Update application")
| where Result =~ "success"
| extend ModProps = TargetResources[0].modifiedProperties
| extend AppName = tostring(TargetResources[0].displayName)
| extend AppId = tostring(TargetResources[0].id)
| extend ActorUpn = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| extend ActorApp = tostring(parse_json(tostring(InitiatedBy.app)).displayName)
| extend ActorIp = iff(
isnotempty(tostring(parse_json(tostring(InitiatedBy.user)).ipAddress)),
tostring(parse_json(tostring(InitiatedBy.user)).ipAddress),
tostring(parse_json(tostring(InitiatedBy.app)).ipAddress))
| extend Actor = iff(isnotempty(ActorUpn), ActorUpn, ActorApp)
| mv-expand ModProp = ModProps
| where tostring(ModProp.displayName) =~ "ReplyUrls"
// newValue is a JSON array of redirect URIs
| extend RawUrls = tostring(ModProp.newValue)
| extend UrlArray = todynamic(RawU
| Sentinel Table | Notes |
|---|---|
AuditLogs | Ensure this data connector is enabled |
Scenario: Scheduled Job for Application Health Check
Description: A scheduled job runs to validate and update redirect URIs for an application, using a third-party tool like Azure AD Connect or a custom script.
Filter/Exclusion: Exclude events where the redirect URI is updated by a known system account (e.g., svc-azureadconnect) or during a specific time window when scheduled jobs are known to run.
Scenario: Admin Task to Update Redirect URI for a Partner Integration
Description: An admin manually updates a redirect URI for a legitimate third-party integration (e.g., a SaaS application used by the company).
Filter/Exclusion: Exclude events where the user is a privileged admin (e.g., [email protected]) and the redirect URI is registered with a known partner or service provider.
Scenario: CI/CD Pipeline Deploying Application Configuration
Description: A CI/CD pipeline (e.g., Azure DevOps, GitHub Actions) deploys a new configuration that includes a redirect URI pointing to an external domain used for development or testing.
Filter/Exclusion: Exclude events where the change is made by a CI/CD service account (e.g., [email protected]) or where the redirect URI is part of a known development environment.
Scenario: Local Development Environment with External Redirect
Description: A developer is testing an application locally and has configured a redirect URI pointing to a local development server (e.g., http://localhost:3000).
Filter/Exclusion: Exclude events where the redirect URI contains localhost or is associated with a local development tool (e.g., ngrok, localhost, or 127.0.0.1).
Scenario: Integration with a Third-Party Identity Provider (IdP)
*