Skip to main content

A hands-on lab deploying detection and hardening for OAuth redirect abuse โ€” the technique Microsoft warned about in their March 2026 advisory.

Cost: Uses an existing Sentinel workspace; ingestion, retention, and licensing charges still apply. Optional hardening changes shared tenant policy. Cleanup: Use the deterministic-ID and ownership-marker destroy path for lab-owned Sentinel objects. If hardening was applied, use the owner-only local manifest to restore the exact captured consent-policy collection and delete only the exact CA policy ID that the script created.

Blog Post: For detailed explanations of the attack technique and detection logic, see Detecting OAuth Redirect Abuse with Microsoft Sentinel and Entra ID.

GitHub: j-dahl7/oauth-redirect-abuse-sentinel

Validation boundary (August 13, 2026): the pinned source passed 20 offline contract tests, including mocked apply, rerun, foreign-policy, partial-failure, consent-race, drift, rollback, and KQL fixture checks. No live tenant mutation, Sentinel query, or incident was validated for this revision.


What Gets Deployed

ResourceTypeDetails
4 Analytics RulesSentinel ScheduledOAuth consent after risky sign-in, suspicious redirect URI, OAuth error patterns, bulk consent
1 WorkbookAzure WorkbookOAuth Security Dashboard (consent timeline, error patterns, URI changes, top apps)
Optional consent policy changeEntra IDTenant authorization-policy update; applied only with -ApplyHardening
Optional CA PolicyEntra IDNewly created report-only step-up policy; same-named policies are never adopted or updated
5 Hunting QueriesKQL filesDelegated permissions audit, non-corporate IPs, new high-priv apps, URI inventory, token replay
1 Audit ScriptPowerShellEnumerate all OAuth apps for suspicious redirect URIs and overprivileged permissions

Prerequisites

  • Azure subscription with an existing Microsoft Sentinel workspace
  • Azure CLI configured (az login)
  • PowerShell 7.3+ (pwsh)
  • Microsoft Sentinel Contributor or equivalent rule/workbook write access on the workspace
  • Directory.Read.All delegated Graph permission and a supported Entra role such as Directory Readers for the OAuth audit (skip with -SkipAudit)
  • For -ApplyHardening, Privileged Role Administrator plus Policy.ReadWrite.Authorization for the tenant consent-policy update
  • For its report-only Conditional Access policy, Conditional Access Administrator (or Security Administrator) plus Policy.Read.All and Policy.ReadWrite.ConditionalAccess
  • At least one reviewed emergency-access user object GUID supplied through -ExcludedUserIds, plus the exact active tenant GUID supplied through -ConfirmTenantId; actual hardening fails closed without either

Quick Start

1. Clone the Repository

git clone https://github.com/j-dahl7/oauth-redirect-abuse-sentinel.git
cd oauth-redirect-abuse-sentinel
git checkout 17cda5d320dd4fe1922c310be4e553d086d9109b

2. Deploy

./scripts/Deploy-Lab.ps1 -ResourceGroup "rg-sentinel-lab" -WorkspaceName "law-sentinel-lab"

Optional tenant hardening:

./scripts/Deploy-Lab.ps1 `
  -ResourceGroup "rg-sentinel-lab" `
  -WorkspaceName "law-sentinel-lab" `
  -ApplyHardening `
  -ConfirmTenantId "<verified-tenant-guid>" `
  -ExcludedUserIds @("<break-glass-user-object-guid>")

Preview the complete deployment without cloud writes, temporary request-body files, or a local audit CSV:

./scripts/Deploy-Lab.ps1 `
  -ResourceGroup "rg-sentinel-lab" `
  -WorkspaceName "law-sentinel-lab" `
  -ApplyHardening `
  -ConfirmTenantId "<verified-tenant-guid>" `
  -ExcludedUserIds @("<break-glass-user-object-guid>") `
  -WhatIf

The script will:

  1. Verify the Sentinel workspace exists and Sentinel is enabled
  2. Deploy 4 scheduled analytics rules via the Sentinel REST API
  3. Deploy the OAuth Security Dashboard workbook
  4. Skip tenant hardening unless -ApplyHardening is present
  5. Run the OAuth app audit and save a CSV report

During -WhatIf, the summary labels resources as planned, skips the audit file, ownership manifest, and temporary request bodies, and applies no local or tenant changes.

3. Verify Deployment

Open Microsoft Defender portal > Microsoft Sentinel > Analytics:

  • You should see 4 new rules prefixed with “LAB -”
  • All rules should show as Enabled with Scheduled type

Open Workbooks:

  • Find “OAuth Security Dashboard” in the list

Analytics Rules

Correlates SigninLogs risk indicators with AuditLogs consent events within a 15-minute window.

MITRE: T1566.002 (Spearphishing Link)

Rule 2: Suspicious OAuth Redirect URI Registered (Medium)

Watches for app registrations adding redirect URIs to tunneling services, free hosting, URL shorteners, or non-HTTPS endpoints. It normalizes normal AppAddress objects and legacy bare-string records, compares oldValue with newValue, and evaluates newly added addresses only. It exempts the exact localhost and 127.0.0.1 HTTP loopback hosts that Microsoft supports for local application development; other HTTP hosts remain suspicious.

MITRE: T1098 (Account Manipulation)

Rule 3: OAuth Error Cluster by Application (Medium)

Groups repeated consent, scope, app-registration, grant, and client-authentication failures by application. These errors can appear during redirect-abuse investigations, but they can also reflect ordinary consent state or broken application configuration. Treat the result as a triage lead and correlate it with redirect-URI changes, consent events, application ownership, and sign-in risk. The error cluster alone does not prove a redirect.

The rule suppresses only immutable AppId values that you explicitly add to ApprovedAppIds; display names are attacker-controlled and are never trusted as an allowlist.

MITRE: None assigned. SigninLogs error codes alone do not establish phishing-link delivery or user execution.

Fires when 3+ distinct, nonempty Entra user object IDs consent to the same app within 1 hour. Repeated events from one account remain in the event count but do not satisfy the distinct-user threshold.

MITRE: T1566.002 (Spearphishing Link)


Hunting Queries

Import the queries from detection/hunting-queries.kql into Sentinel Hunting:

HuntPurposeLookback
1. Enumerate Delegated PermissionsBaseline audit of all user-granted permissions90 days
2. Non-Corporate IP Sign-insOAuth app auth from unexpected locations30 days
3. New High-Privilege AppsRecently registered apps with sensitive scopes14 days
4. Redirect URI InventoryFull audit trail of redirect URI changes90 days
5. Token Replay After ErrorError redirect followed by successful auth from different IP7 days

Hunt 2 requires customization โ€” replace the CorporateNetworks variable with your organization’s IP ranges.


Hardening Policies

The Set-OAuthHardening.ps1 script restricts user consent to:

  • Low-risk permissions only (e.g., User.Read, openid, profile)
  • Apps from verified publishers and trusted tenant-owned workflows
  • Everything else requires admin approval
  • Existing managePermissionGrantsForOwnedResource.* entries are preserved when the policy is updated

Before its first Graph mutation, the script persists an owner-only manifest with the exact tenant, original and intended consent-policy collections, intended CA content hash, and reviewed exclusions. Immediately after Graph creates the CA policy, the script atomically records its server-assigned immutable ID before changing the authorization policy. It verifies the server-created policy by ID and hash, then re-reads consent immediately before and after its guarded update. The manifest and temporary Graph request bodies are locked to the current owner before sensitive content is written.

Conditional Access Policy

Creates a new report-only lab CA policy that applies when:

  • Sign-in risk is Medium or High
  • Grant controls require MFA
  • Session sign-in frequency is set to Every time
  • Emergency-access users supplied through -ExcludedUserIds are excluded

Supply break-glass exclusions before creating the policy, review report-only results for at least 7 days, and validate impact before considering enforcement. The script never adopts or updates a policy by display name. Same-name foreign policies, tenant mismatches, changed exclusions, an uncertain prior create, and consent or CA content drift all stop the operation before another write. Declining an interactive confirmation aborts without reporting false success.

OAuth App Audit

Run the audit independently:

./hardening/Audit-OAuthApps.ps1 -OutputPath "./oauth-audit-report.csv"

The audit checks every app registration for:

  • Suspicious redirect URI domains (ngrok, herokuapp, workers.dev, etc.)
  • Non-HTTPS redirect URIs (excluding localhost)
  • High-privilege delegated permissions (Mail.Read, Files.ReadWrite.All, etc.)
  • User-consented vs admin-consented permissions
  • Multi-tenant app registrations

Output is a CSV sorted by risk score.


File Structure

oauth-redirect-abuse-sentinel/
โ”œโ”€โ”€ README.md                             # Lab documentation
โ”œโ”€โ”€ detection/
โ”‚   โ”œโ”€โ”€ analytics-rules.kql            # 4 Sentinel analytics rules (full KQL)
โ”‚   โ””โ”€โ”€ hunting-queries.kql            # 5 proactive hunting queries
โ”œโ”€โ”€ hardening/
โ”‚   โ”œโ”€โ”€ Set-OAuthHardening.ps1         # Consent restriction + CA policy
โ”‚   โ””โ”€โ”€ Audit-OAuthApps.ps1            # OAuth app security audit
โ””โ”€โ”€ scripts/
    โ””โ”€โ”€ Deploy-Lab.ps1                 # Main deployment orchestrator

Cleanup

Remove Sentinel Resources

Preview cleanup of the four deterministic, ownership-marked rules and workbook:

./scripts/Deploy-Lab.ps1 `
  -ResourceGroup "rg-sentinel-lab" `
  -WorkspaceName "law-sentinel-lab" `
  -Destroy `
  -WhatIf

Then run the same ownership checks and delete only the exact owned objects:

./scripts/Deploy-Lab.ps1 `
  -ResourceGroup "rg-sentinel-lab" `
  -WorkspaceName "law-sentinel-lab" `
  -Destroy

Cleanup refuses same-title foreign objects and mismatched IDs or ownership markers. Do not bulk-delete every rule prefixed LAB -.

Remove Hardening (if applied)

Preview the manifest-backed, drift-aware rollback:

./hardening/Set-OAuthHardening.ps1 `
  -ConfirmTenantId "<verified-tenant-guid>" `
  -Rollback `
  -WhatIf

Then omit -WhatIf. The script restores only the exact captured consent collection and deletes only the manifest-owned CA ID after verifying its full managed content hash. It re-reads consent immediately before restoration and re-reads/re-hashes the exact CA object immediately before deletion. It refuses drift and never deletes by name; a completed rollback can be rerun safely with no additional cloud mutation. The Sentinel destroy path intentionally does not guess or revert tenant-wide Graph state, and it does not delete the local oauth-audit-report.csv.


Troubleshooting

Rules Don’t Fire

Analytics rules need matching data in SigninLogs and AuditLogs. If you don’t have OAuth consent events or risky sign-ins in your tenant, the rules will be silent. Test by:

  1. Registering a test app with a redirect URI containing webhook.site (triggers Rule 2)
  2. Checking that AuditLogs contains “Add application” events

Workbook Shows No Data

Ensure the workspace has AuditLogs and SigninLogs data connectors enabled. Check:

AuditLogs | take 1
SigninLogs | take 1

Hardening Script Fails

The hardening script changes two policy surfaces. Updating the tenant authorization policy requires Privileged Role Administrator and Policy.ReadWrite.Authorization. Creating its report-only Conditional Access policy requires Conditional Access Administrator (or Security Administrator) plus Policy.Read.All and Policy.ReadWrite.ConditionalAccess. Run with -WhatIf to preview changes:

./hardening/Set-OAuthHardening.ps1 `
  -ConfirmTenantId "<verified-tenant-guid>" `
  -ExcludedUserIds @("<break-glass-user-object-guid>") `
  -WhatIf

This is a true preview: it performs the read-only discovery needed to build the plan, but does not write Graph changes or temporary request-body files.


Resources