Entra ID Device-Bound PRT from Unusual Device IP


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.

Query · kuery

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"
    )

Investigation fields

Pivot points the source recommends for triage.

  • 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

Implementation guide

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 and the Azure integration.

Known 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.

Analyst notes

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.
Raw source Entra ID Device-Bound PRT from Unusual Device IP · Elastic TOML
Esc
Published by elastic/detection-rules ↗, licensed under Elastic License 2.0 ↗. Reproduced here unmodified.
[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"


Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.