← Back to Explore
elastichighTTP
Entra ID Device-Bound PRT from Unusual Device IP
Detects 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 a source IP that has not been seen with that deviceid. Replay events are limited to compliant or Intune-managed devices. Adversaries who steal a WAM PRT SSO cookie replay it off-box; the token keeps the workstation deviceid, so this pair is new even when Windows Sign-In for that device is outside a correlation window.
Detection Query
data_stream.dataset: azure.signinlogs and
event.outcome: success and
azure.signinlogs.properties.status.error_code: 0 and
azure.signinlogs.properties.incoming_token_type: "primaryRefreshToken" and
azure.signinlogs.properties.device_detail.device_id: * and
source.ip: * and
(
azure.signinlogs.properties.device_detail.is_compliant: true or
azure.signinlogs.properties.device_detail.is_managed: true
) and azure.signinlogs.properties.app_id: (
"04b07795-8ddb-461a-bbee-02f9e1bf7b46" or
"1950a258-227b-4e31-a9cf-717495945fc2" or
"aebc6443-996d-45c2-90f0-388ff96faa56" or
"14d82eec-204b-4c2f-b7e8-296a70dab67e" or
"1b730954-1685-4b74-9bfd-dac224a7b894" or
"872cd9fa-d31f-45e0-9eab-6e460a02d1f1"
) and azure.signinlogs.properties.resource_id: (
"00000003-0000-0000-c000-000000000000" or
"00000003-0000-0ff1-ce00-000000000000" or
"6a9b9266-8161-4a7b-913a-a9eda19da220" or
"00000002-0000-0ff1-ce00-000000000000"
)
Author
Elastic
Created
2026/08/28
Data Sources
AzureMicrosoft Entra IDMicrosoft Entra ID Sign-In Logsfilebeat-*logs-azure.signinlogs-*
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: CloudDomain: IdentityUse Case: Threat DetectionUse Case: Identity and Access AuditData Source: AzureData Source: Microsoft Entra IDData Source: Microsoft Entra ID Sign-In LogsPlatform: Entra IDTactic: Credential AccessTactic: Defense EvasionResources: Investigation GuideRule Type: New Terms
Raw Content
[metadata]
creation_date = "2026/08/28"
integration = ["azure"]
maturity = "production"
updated_date = "2026/09/18"
[rule]
author = ["Elastic"]
description = """
Detects 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 a source IP that has not been seen with that deviceid. Replay events are limited to compliant or
Intune-managed devices. Adversaries who steal a WAM PRT SSO cookie replay it off-box; the token keeps the workstation
deviceid, so this pair is new even when Windows Sign-In for that device is outside a correlation window.
"""
false_positives = [
"""
A user who runs Azure CLI, Azure PowerShell, VS Code, Graph CLI, or Visual Studio from a new egress (VPN, hotel,
Cloud Shell, jump host) while WAM still attaches a compliant or managed workstation deviceid will match on first
sight of that IP. Exception: add known developer and Cloud Shell ranges after confirming the client ran there.
""",
"""
Split-tunnel or dual-homed devices can present a new Microsoft 365 egress for tooling clients. Confirm the IP
belongs to the enrolled device before treating the event as cookie theft.
""",
"""
Microsoft Teams, Office, OneDrive SyncEngine, Authentication Broker, Outlook Mobile, Bing, Azure Portal, and Office
365 Management are omitted because first sight of a mobile or M365 egress IP for those clients is routine.
""",
]
from = "now-9m"
index = ["filebeat-*", "logs-azure.signinlogs-*"]
language = "kuery"
license = "Elastic License v2"
name = "Entra ID Device-Bound PRT from Unusual Device IP"
note = """## Triage and analysis
### Investigating Entra ID Device-Bound PRT from Unusual Device IP
This rule fires when a tooling FOCI client redeems a device-bound PRT from a `source.ip` that has not appeared with that `device_id` in the prior 5 days. Unlike **Entra ID Device-Bound PRT Replay via First-Party App from Unusual IP**, it does not need a Windows Sign-In or WAM event in the same window — so a workstation that last signed in yesterday still alerts when the cookie is redeemed from a new address.
PRTremote and similar harvest tools steal a WAM PRT SSO cookie and POST it from attacker infrastructure. The issued token keeps the *workstation* deviceid, so Conditional Access that requires a compliant device can succeed. Replay events must be `is_compliant` or `is_managed`. The stolen cookie nonce is typically valid for about five minutes; that is the attacker’s redeem window, not this rule’s history.
This is not ConsentFix / OAuth-code phishing. Those flows show `OAuth2:Authorize` with `Redirect` and often lack a bound compliant device.
### Possible investigation steps
- Review `azure.signinlogs.properties.user_principal_name`, `azure.signinlogs.properties.device_detail.device_id`, `azure.signinlogs.properties.device_detail.display_name`, and `source.ip`. Confirm `incoming_token_type` is `primaryRefreshToken` and `device_detail.is_compliant` / `is_managed` are true.
- Compare `azure.signinlogs.properties.app_display_name` and `user_agent.original` with the resource (`resource_display_name`). 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 Windows `az login` uses when it brokers through WAM, so it is triage context, not a detection key. TokenTactics commonly presents as Microsoft Office (that client is omitted here; hunt it separately if harvest is already confirmed).
- Hunt prior Windows Sign-In / `Windows-AzureAD-Authentication-Provider/1.0` events for the same deviceid. If those IPs differ from `source.ip`, treat this as the same story as the ES|QL companion. A Microsoft-owned ASN (including 8075) does not clear the alert.
- Hunt endpoint telemetry for the same user or device display name as `host.name`: InteractiveToken scheduled tasks, `svchost.exe` (Schedule) → `cmd.exe` → `BrowserCore.exe`, or files `formatted_nonce.txt` / `prt_cookie.txt`.
- Review Graph, SharePoint, and mailbox activity after the sign-in for directory, file, or mail enumeration.
### False positive analysis
- Developers who run Azure CLI, Graph CLI, or VS Code from a new network while WAM still attaches the workstation deviceid. Exception known Cloud Shell / jump-host IPs after confirming the client actually ran there.
- First use of a tooling client on a new device, or the first time a laptop appears on a new ISP, will fire once per `(device_id, source.ip)` pair and then age out of novelty.
- Hybrid-joined workstations that report both `is_compliant` and `is_managed` as false (no Intune) will not match. Hunt those deviceids separately if harvest is already confirmed.
- Microsoft Teams, Office, OneDrive SyncEngine, Authentication Broker, Outlook Mobile, Bing, Azure Portal, and Office 365 Management are excluded. Do not treat their absence as a miss.
### Response and remediation
- Contact the user to confirm whether they ran the first-party client from `source.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.
"""
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",
]
risk_score = 73
rule_id = "9e8d9bb5-6d3a-4431-a0d0-8d0475e726a4"
setup = """The Azure Fleet integration (or Filebeat Azure module) with Microsoft Entra ID sign-in logs is required. Ingest `SignInLogs` and `NonInteractiveUserSignInLogs` into `logs-azure.signinlogs-*`.
See [Microsoft Entra ID sign-in logs](https://learn.microsoft.com/en-us/entra/identity/monitoring-health/concept-sign-ins) and the [Azure integration](https://docs.elastic.co/integrations/azure).
"""
severity = "high"
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",
"Resources: Investigation Guide",
"Rule Type: New Terms",
]
timestamp_override = "event.ingested"
type = "new_terms"
query = '''
data_stream.dataset: azure.signinlogs and
event.outcome: success and
azure.signinlogs.properties.status.error_code: 0 and
azure.signinlogs.properties.incoming_token_type: "primaryRefreshToken" and
azure.signinlogs.properties.device_detail.device_id: * and
source.ip: * and
(
azure.signinlogs.properties.device_detail.is_compliant: true or
azure.signinlogs.properties.device_detail.is_managed: true
) and azure.signinlogs.properties.app_id: (
"04b07795-8ddb-461a-bbee-02f9e1bf7b46" or
"1950a258-227b-4e31-a9cf-717495945fc2" or
"aebc6443-996d-45c2-90f0-388ff96faa56" or
"14d82eec-204b-4c2f-b7e8-296a70dab67e" or
"1b730954-1685-4b74-9bfd-dac224a7b894" or
"872cd9fa-d31f-45e0-9eab-6e460a02d1f1"
) and azure.signinlogs.properties.resource_id: (
"00000003-0000-0000-c000-000000000000" or
"00000003-0000-0ff1-ce00-000000000000" or
"6a9b9266-8161-4a7b-913a-a9eda19da220" or
"00000002-0000-0ff1-ce00-000000000000"
)
'''
[[rule.threat]]
framework = "MITRE ATT&CK"
[[rule.threat.technique]]
id = "T1528"
name = "Steal Application Access Token"
reference = "https://attack.mitre.org/techniques/T1528/"
[[rule.threat.technique]]
id = "T1539"
name = "Steal Web Session Cookie"
reference = "https://attack.mitre.org/techniques/T1539/"
[rule.threat.tactic]
id = "TA0006"
name = "Credential Access"
reference = "https://attack.mitre.org/tactics/TA0006/"
[[rule.threat]]
framework = "MITRE ATT&CK"
[[rule.threat.technique]]
id = "T1550"
name = "Use Alternate Authentication Material"
reference = "https://attack.mitre.org/techniques/T1550/"
[[rule.threat.technique.subtechnique]]
id = "T1550.001"
name = "Application Access Token"
reference = "https://attack.mitre.org/techniques/T1550/001/"
[rule.threat.tactic]
id = "TA0005"
name = "Defense Evasion"
reference = "https://attack.mitre.org/tactics/TA0005/"
[[rule.threat]]
framework = "MITRE ATT&CK"
[[rule.threat.technique]]
id = "T1078"
name = "Valid Accounts"
reference = "https://attack.mitre.org/techniques/T1078/"
[[rule.threat.technique.subtechnique]]
id = "T1078.004"
name = "Cloud Accounts"
reference = "https://attack.mitre.org/techniques/T1078/004/"
[rule.threat.tactic]
id = "TA0001"
name = "Initial Access"
reference = "https://attack.mitre.org/tactics/TA0001/"
[rule.investigation_fields]
field_names = [
"azure.signinlogs.properties.user_principal_name",
"azure.signinlogs.properties.user_id",
"azure.signinlogs.properties.device_detail.device_id",
"azure.signinlogs.properties.device_detail.display_name",
"azure.signinlogs.properties.app_id",
"azure.signinlogs.properties.app_display_name",
"azure.signinlogs.properties.resource_id",
"azure.signinlogs.properties.resource_display_name",
"azure.signinlogs.properties.incoming_token_type",
"azure.signinlogs.properties.device_detail.is_compliant",
"azure.signinlogs.properties.device_detail.is_managed",
"azure.signinlogs.properties.authentication_requirement",
"azure.signinlogs.properties.conditional_access_status",
"source.ip",
"source.geo.country_name",
"user_agent.original",
]
[rule.new_terms]
field = "new_terms_fields"
value = ["azure.signinlogs.properties.device_detail.device_id", "source.ip"]
[[rule.new_terms.history_window_start]]
field = "history_window_start"
value = "now-5d"