Entra ID Device-Bound PRT Replay via First-Party App from Unusual IP
editEntra ID Device-Bound PRT Replay via First-Party App from Unusual IP
editDetects a first-party FOCI tooling client (Azure CLI, PowerShell, VS Code, Graph CLI, Azure AD PowerShell, or Visual Studio) redeeming a device-bound Primary Refresh Token (PRT) for Microsoft Graph, SharePoint/OneDrive, or Exchange Online from an IP that is not among that user and device’s Windows Sign-In or WAM addresses. Replay events are limited to compliant or Intune-managed devices: the stolen cookie keeps the workstation deviceid, so compliant-device Conditional Access can succeed off-box.
Rule type: esql
Rule indices: None
Severity: high
Risk score: 73
Runs every: 1h
Searches indices from: now-24h (Date Math format, see also Additional look-back time)
Maximum alerts per execution: 100
References:
- https://www.armadin.com/blog-posts/prtremote-extract-prt-cookies-remotely-with-interactivetoken-scheduled-task
- https://github.com/armadin-public/PRTremote
- https://github.com/dmcxblue/ANIMO/blob/master/helpers/scripts/GrabTokenAzureAD/PrtExtractor.cs
- https://github.com/rvrsh3ll/TokenTactics
- https://github.com/Gerenios/AADInternals
Tags:
- Domain: Cloud
- Domain: Identity
- Use Case: Threat Detection
- Use Case: Identity and Access Audit
- Data Source: Azure
- Data Source: Microsoft Entra ID
- Data Source: Microsoft Entra ID Sign-in Logs
- Platform: Entra ID
- Tactic: Credential Access
- Tactic: Defense Evasion
- Tactic: Initial Access
- Resources: Investigation Guide
- Rule Type: ESQL
Version: 1
Rule authors:
- Elastic
Rule license: Elastic License v2
Investigation guide
editTriage and analysis
Investigating Entra ID Device-Bound PRT Replay via First-Party App from Unusual IP
Adversaries who steal a WAM PRT SSO cookie (BrowserCore / InteractiveToken harvest) redeem it off-box for Microsoft Graph, SharePoint/OneDrive, or Exchange Online using a first-party FOCI client. PRTremote uses Azure CLI (04b07795-8ddb-461a-bbee-02f9e1bf7b46); TokenTactics and AADInternals can use Azure AD PowerShell (1b730954-1685-4b74-9bfd-dac224a7b894) or Microsoft Office (d3590ed6-52b3-4102-aeff-aad2292ab01c) when refreshing a PRT into a Graph, Outlook, or SharePoint token. This rule keys on CLI / PowerShell / VS Code / Visual Studio, not Teams, Office, OneDrive SyncEngine, Authentication Broker, Outlook Mobile, Bing, or Azure Portal — those clients routinely egress on a different IP than Windows Sign-In. Hunt Office-client PRT separately if harvest is already confirmed. The issued access token carries the workstation deviceid, so Conditional Access policies that require a compliant device can succeed even though the redeeming IP is not the device. Replay events must be is_compliant or is_managed so the join tracks a stolen enrolled workstation PRT, not an unmanaged ROADtx device.
This is not ConsentFix / OAuth-code phishing. Those flows show OAuth2:Authorize with Redirect and often lack a bound compliant device on the Graph token. Do not close this alert by following the first-party OAuth phishing playbook alone.
Possible investigation steps
-
Review
azure.signinlogs.properties.user_principal_name,azure.signinlogs.properties.device_detail.device_id, andsource.ip. Confirmincoming_token_typeon the replay events isprimaryRefreshTokenanddevice_detail.is_compliant/is_managedare true.Esql.resource_display_name_valuesshould be Microsoft Graph, Office 365 SharePoint Online, OneDrive for Business, and/or Office 365 Exchange Online. -
Treat
Esql.source_ip_device_valuesas the workstation (Windows Sign-In /Windows-AzureAD-Authentication-Provider/1.0) — the device the PRT was bound to, and the likely theft origin. Treatsource.ip/Esql.source_ip_replay_valuesas Graph / SharePoint / Exchange PRT IPs that are not in that device-session set (the replay actor). On-box FOCI from the workstation IP is excluded from the replay set. The harvest emulation showed workstation public IP versus attacker SNAT — including Azure ASN 8075, so a Microsoft-owned ASN does not clear the alert. The stolen PRT cookie nonce is typically valid for about five minutes; that is the attacker’s redeem window, not this rule’s lookback. The workstation may have signed in hours earlier, so the query correlates 24 hours of Windows Sign-In / WAM IPs with the replay. -
Inspect
Esql.app_display_name_valuesandEsql.user_agent_original_values. A cookie POST through WAM can show a Trident/MSIE user agent on the Azure CLI client ID; that is the same IE stack legitimate Windowsaz loginuses when it brokers through WAM, so it is triage context, not a detection key. TokenTactics Graph refresh commonly presents as Microsoft Office (omitted here). A follow-onpython-requests/*Graph token from the same session is tooling, not the first-party binary on the workstation. -
Hunt endpoint telemetry for the same user or
device_detail.display_nameashost.name:svchost.exe(Schedule) →cmd.exe→BrowserCore.exewith</>redirection, or filesformatted_nonce.txt/prt_cookie.txt. -
Intune audit (
create-clientcertificate) is enrollment context only; it will not fire on the harvest. Use it as optional join on user OID, not as coverage. - Review Graph, SharePoint, and mailbox activity after the replay for directory, file, or mail enumeration.
False positive analysis
- Developers who run Azure CLI, Graph CLI, or VS Code from a different egress than Windows Sign-In while WAM still attaches the workstation deviceid. Exception known Cloud Shell / jump-host IPs after confirming the client actually ran there.
- Microsoft Teams, Office, OneDrive SyncEngine, Authentication Broker, Outlook Mobile, Bing, Azure Portal, and Office 365 Management Graph PRT are excluded. Do not treat their absence as a miss; they are omitted because split-tunnel M365 egress is routine.
- First sign-in of a new device can have sparse Windows Sign-In history in the 24-hour window; widen the hunt before responding.
-
Hybrid-joined workstations that report both
is_compliantandis_managedas false (no Intune) will not match the replay branch. Hunt those deviceids separately if harvest is already confirmed.
Response and remediation
-
Contact the user to confirm whether they ran the first-party client in
Esql.app_display_name_valuesfrom the replay IP. - If unauthorized, revoke refresh tokens and primary refresh tokens for the user. The deviceid on the token is often the legitimate workstation — do not delete that device as if it were a ROADtx registration until you confirm otherwise.
- Isolate the workstation, hunt for InteractiveToken scheduled tasks and BrowserCore harvest, and treat the admin identity that registered the task as a second compromised principal.
Setup
editThe Azure Fleet integration (or Filebeat Azure module) with Microsoft Entra ID sign-in logs is required. Ingest SignInLogs and NonInteractiveUserSignInLogs into logs-azure.signinlogs-* so Windows Sign-In / WAM device activity and FOCI Graph, SharePoint, and Exchange token issuance are both available for the join.
See Microsoft Entra ID sign-in logs and the Azure integration.
Rule query
editfrom logs-azure.signinlogs-*
// find successful sign-in events where a managed device exists
| where event.dataset == "azure.signinlogs"
and azure.signinlogs.properties.status.error_code == 0
and azure.signinlogs.properties.device_detail.device_id is not null
// filter for device sign-in events from Windows Sign-In or WAM (login session)
| eval Esql.is_device_session = azure.signinlogs.properties.app_display_name == "Windows Sign In"
or user_agent.original == "Windows-AzureAD-Authentication-Provider/1.0"
// filter for tooling FOCI clients that can redeem a PRT (replay)
| eval Esql.is_prt_replay = azure.signinlogs.properties.app_id in (
"04b07795-8ddb-461a-bbee-02f9e1bf7b46", // Microsoft Azure CLI
"1950a258-227b-4e31-a9cf-717495945fc2", // Microsoft Azure PowerShell
"aebc6443-996d-45c2-90f0-388ff96faa56", // Visual Studio Code
"14d82eec-204b-4c2f-b7e8-296a70dab67e", // Microsoft Graph Command Line Tools
"1b730954-1685-4b74-9bfd-dac224a7b894", // Azure Active Directory PowerShell
"872cd9fa-d31f-45e0-9eab-6e460a02d1f1" // Visual Studio
)
// target resource are common adversary targets for access
and azure.signinlogs.properties.resource_id in (
"00000003-0000-0000-c000-000000000000", // Microsoft Graph
"00000003-0000-0ff1-ce00-000000000000", // Office 365 SharePoint Online
"6a9b9266-8161-4a7b-913a-a9eda19da220", // OneDrive for Business
"00000002-0000-0ff1-ce00-000000000000" // Office 365 Exchange Online
)
and azure.signinlogs.properties.incoming_token_type == "primaryRefreshToken"
and (
azure.signinlogs.properties.device_detail.is_compliant == true
or azure.signinlogs.properties.device_detail.is_managed == true
)
// device session or PRT replay event have to exist
| where Esql.is_device_session or Esql.is_prt_replay
// aggregate entities for both device session and PRT replay events
// aggregate by user and device
| stats
Esql.source_ip_device_values = values(source.ip) where Esql.is_device_session,
Esql.source_ip_replay_values = values(source.ip) where Esql.is_prt_replay,
Esql.event_count_replay = count(*) where Esql.is_prt_replay,
Esql.event_count_device_session = count(*) where Esql.is_device_session,
Esql.user_principal_name_values = values(azure.signinlogs.properties.user_principal_name),
Esql.app_display_name_values = values(azure.signinlogs.properties.app_display_name) where Esql.is_prt_replay,
Esql.resource_id_values = values(azure.signinlogs.properties.resource_id) where Esql.is_prt_replay,
Esql.resource_display_name_values = values(azure.signinlogs.properties.resource_display_name) where Esql.is_prt_replay,
Esql.device_display_name_values = values(azure.signinlogs.properties.device_detail.display_name),
Esql.user_agent_original_values = values(user_agent.original) where Esql.is_prt_replay,
Esql.device_is_compliant_values = values(azure.signinlogs.properties.device_detail.is_compliant) where Esql.is_prt_replay,
Esql.device_is_managed_values = values(azure.signinlogs.properties.device_detail.is_managed) where Esql.is_prt_replay,
Esql.authentication_requirement_values = values(azure.signinlogs.properties.authentication_requirement) where Esql.is_prt_replay,
Esql.conditional_access_status_values = values(azure.signinlogs.properties.conditional_access_status) where Esql.is_prt_replay,
Esql.earliest_timestamp = min(@timestamp),
Esql.latest_timestamp = max(@timestamp)
by azure.signinlogs.properties.user_id, azure.signinlogs.properties.device_detail.device_id
// filter for PRT replay events that have at least one replay IP
| where Esql.event_count_replay > 0
and Esql.event_count_device_session > 0
and Esql.source_ip_replay_values is not null
and Esql.source_ip_device_values is not null
// expand the replay IP list and keep only IPs that are not in the device-session set
| mv_expand Esql.source_ip_replay_values
| where not mv_contains(Esql.source_ip_device_values, Esql.source_ip_replay_values)
| eval Esql.source_ip_replay = Esql.source_ip_replay_values,
user.id = azure.signinlogs.properties.user_id,
source.ip = Esql.source_ip_replay_values
| keep
user.id,
source.ip,
azure.signinlogs.properties.user_id,
azure.signinlogs.properties.device_detail.device_id,
Esql.*
Framework: MITRE ATT&CKTM
-
Tactic:
- Name: Credential Access
- ID: TA0006
- Reference URL: https://attack.mitre.org/tactics/TA0006/
-
Technique:
- Name: Steal Application Access Token
- ID: T1528
- Reference URL: https://attack.mitre.org/techniques/T1528/
-
Technique:
- Name: Steal Web Session Cookie
- ID: T1539
- Reference URL: https://attack.mitre.org/techniques/T1539/
-
Tactic:
- Name: Defense Evasion
- ID: TA0005
- Reference URL: https://attack.mitre.org/tactics/TA0005/
-
Technique:
- Name: Use Alternate Authentication Material
- ID: T1550
- Reference URL: https://attack.mitre.org/techniques/T1550/
-
Sub-technique:
- Name: Application Access Token
- ID: T1550.001
- Reference URL: https://attack.mitre.org/techniques/T1550/001/
-
Tactic:
- Name: Initial Access
- ID: TA0001
- Reference URL: https://attack.mitre.org/tactics/TA0001/
-
Technique:
- Name: Valid Accounts
- ID: T1078
- Reference URL: https://attack.mitre.org/techniques/T1078/
-
Sub-technique:
- Name: Cloud Accounts
- ID: T1078.004
- Reference URL: https://attack.mitre.org/techniques/T1078/004/