T1586

Compromise Accounts

Resource Development Last updated:

This detection identifies indicators of compromised accounts being leveraged against the organization, including credential stuffing attacks that transition from repeated failures to success, impossible travel anomalies where a single identity authenticates from geographically distant locations within an implausible timeframe, sign-ins from known hosting or anonymization infrastructure, and MFA bypass patterns consistent with session token theft or adversary-in-the-middle phishing toolkits such as Evilginx2 or Modlishka. Because T1586 is a PRE-ATT&CK technique occurring outside the victim environment, detections focus on the observable authentication artifacts generated when adversaries weaponize stolen credentials or session material against organizational identity providers including Azure AD, on-premises Active Directory, and SaaS application login flows.

What is T1586 Compromise Accounts?

Compromise Accounts (T1586) maps to the Resource Development tactic — the adversary is trying to establish resources they can use to support operations in MITRE ATT&CK.

This page provides production-ready detection logic for Compromise Accounts, covering the data sources and telemetry it touches: Azure Active Directory, Microsoft Entra ID. The queries below are rated high severity at medium confidence, and ship for 7 SIEM platforms — KQL, SPL, Elastic, QRadar, Sumo, YARA-L, LogScale.

MITRE ATT&CK

Tactic
Resource Development
Technique
T1586 Compromise Accounts
Canonical reference
https://attack.mitre.org/techniques/T1586/
Microsoft Sentinel / Defender
kusto
let timeWindow = 24h;
let failThreshold = 5;
let travelWindowMinutes = 60;
// --- Signal 1: Credential stuffing — many failures then success from multiple IPs ---
let CredentialStuffing = AADSignInLogs
| where TimeGenerated > ago(timeWindow)
| where ResultType != "50053" and ResultType != "50076" // exclude locked-out and MFA-required noise
| summarize
    FailureCount = countif(ResultType != "0"),
    SuccessCount = countif(ResultType == "0"),
    UniqueIPs = dcount(IPAddress),
    IPList = make_set(IPAddress, 10),
    Apps = make_set(AppDisplayName, 5),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated)
    by UserPrincipalName
| where FailureCount >= failThreshold and SuccessCount >= 1
| extend DetectionType = "CredentialStuffing",
    RiskScore = iff(UniqueIPs > 5, 90, iff(UniqueIPs > 2, 75, 60));
// --- Signal 2: Impossible travel — successful logins from 2+ countries within 60 minutes ---
let ImpossibleTravel = AADSignInLogs
| where TimeGenerated > ago(timeWindow)
| where ResultType == "0"
| where isnotempty(Location)
| extend Country = tostring(LocationDetails.countryOrRegion)
| where isnotempty(Country)
| summarize
    Countries = make_set(Country, 20),
    IPList = make_set(IPAddress, 10),
    Apps = make_set(AppDisplayName, 5),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated),
    SuccessCount = count()
    by UserPrincipalName, bin(TimeGenerated, 1h)
| where array_length(Countries) > 1
| extend DetectionType = "ImpossibleTravel", RiskScore = 85,
    FailureCount = 0, UniqueIPs = array_length(IPList);
// --- Signal 3: Successful login from Tor exit node or known bulletproof hosting ASN ---
let SuspiciousASN = AADSignInLogs
| where TimeGenerated > ago(timeWindow)
| where ResultType == "0"
| extend ASN = tostring(todynamic(NetworkLocationDetails)[0].networkNames)
| where ASN has_any ("M247", "Frantech", "Sharktech", "Psychz", "Quasi Networks",
    "FranTech", "Alexhost", "ITL-Bulgaria", "Serverius", "Combahton")
    or IPAddress matches regex @"^185\.(220|129|220)\."
| summarize
    IPList = make_set(IPAddress, 10),
    Apps = make_set(AppDisplayName, 5),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated),
    SuccessCount = count(),
    Countries = make_set(tostring(LocationDetails.countryOrRegion), 5)
    by UserPrincipalName
| extend DetectionType = "SuspiciousHostingASN", RiskScore = 80,
    FailureCount = 0, UniqueIPs = array_length(IPList);
// --- Union all signals ---
CredentialStuffing
| union ImpossibleTravel
| union SuspiciousASN
| project
    TimeGenerated = LastSeen,
    UserPrincipalName,
    DetectionType,
    RiskScore,
    FailureCount,
    SuccessCount,
    UniqueIPs,
    IPList,
    Apps,
    Countries,
    FirstSeen,
    LastSeen
| sort by RiskScore desc, TimeGenerated desc

Detects three distinct compromise account patterns against Azure AD: (1) credential stuffing where 5+ authentication failures from multiple IPs precede a successful login, (2) impossible travel where a single identity successfully authenticates from two or more countries within a 60-minute window, and (3) successful logins sourced from known bulletproof hosting or anonymization ASNs associated with threat actor infrastructure. Results are scored by risk level to prioritize analyst triage.

high severity medium confidence

Data Sources

Azure Active Directory Microsoft Entra ID

Required Tables

AADSignInLogs

False Positives

  • Legitimate corporate VPN services routing authentication through shared exit nodes may match ASN-based detection; allowlist known corporate egress IP ranges
  • Traveling employees authenticating from multiple countries within a short window (e.g. connecting through an airline hub) will trigger impossible travel; cross-reference with HR travel records and conditional access named locations
  • Shared service accounts used by automation platforms (CI/CD, monitoring) may generate high failure counts if misconfigured credentials are in rotation before being corrected; baseline service account authentication patterns
  • Password reset self-service workflows may generate multiple ResultType failures before a successful reset, mimicking credential stuffing; filter on UserType and correlate with SSPR audit events in AuditLogs

Sigma rule & cross-platform mapping

The detection logic for Compromise Accounts (T1586) above is provided in a vendor-neutral form so you can deploy it on any SIEM. The same logic is shipped here as native KQL (Microsoft Sentinel / Defender), SPL (Splunk), Elastic (Elastic Security (EQL)), QRadar (IBM QRadar (AQL)), Sumo (Sumo Logic CSE), YARA-L (Google Chronicle / SecOps), LogScale (CrowdStrike LogScale (CQL)) queries. In Sigma terms, this detection targets the following logsource:

logsource:
  product: azure

Browse the community-maintained Sigma rules for this technique:


Testing Methodology

Validate this detection against 3 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.

  1. Test 1Simulate credential stuffing authentication pattern using PowerShell against Azure AD

    Expected signal: AADSignInLogs will show 6 ResultType != 0 events followed by 1 ResultType == 0 event for the test UPN from the same source IP within a short time window. RiskState may update in AADRiskyUsers within 15-30 minutes.

  2. Test 2Simulate legacy protocol authentication bypass against Exchange Online (SMTP AUTH)

    Expected signal: AADSignInLogs entry with ClientAppUsed='Authenticated SMTP', AuthenticationRequirement='singleFactorAuthentication', ResultType=0 for the test account. This event will NOT appear in modern auth logs, validating the legacy auth gap.

  3. Test 3Simulate account compromise indicators via failed then successful Windows network logon from multiple sources

    Expected signal: Windows Security EventID 4625 (LogonType 3, SubStatus 0xC000006A = wrong password) six times followed by EventID 4624 (LogonType 3) once for the test account in the domain controller Security event log. Source workstation will be the executing host.


Response Playbook

Triage

  1. Step 1 — Identify the detection subtype (CredentialStuffing, ImpossibleTravel, SuspiciousASN) and pull the full sign-in log for the flagged UserPrincipalName covering the past 72 hours. Note all source IPs, user agents, client application IDs, and conditional access outcomes.
  2. Step 2 — For CredentialStuffing: check whether the failures preceded the success by a consistent interval (automated tooling) or appear random (manual); extract the User-Agent header from AADSignInLogs to identify known attack frameworks (e.g., python-requests, curl, go-http).
  3. Step 3 — For ImpossibleTravel: calculate the physical distance between the two authentication locations and the time delta. If a human cannot travel that distance in that time, treat as confirmed compromise. Cross-reference with the user's recent travel requests in HR systems or calendar.
  4. Step 4 — Query OfficeActivity and CloudAppEvents for the user for the 48 hours following the suspicious successful login. Look for mass email access, large file downloads, permission changes, or OAuth app consent grants that occurred under the compromised session.
  5. Step 5 — Check AuditLogs for MFA registration changes, password resets, conditional access named location changes, or trusted device additions that occurred within 30 minutes of the suspicious successful login — these indicate the adversary is establishing persistence.
  6. Step 6 — Correlate the source IP(s) against threat intelligence feeds (VirusTotal, Shodan, AbuseIPDB, Spamhaus) to assess whether it belongs to a residential proxy network, VPN exit node, or known C2 infrastructure. Check if the same IP appeared in failed logins against other accounts.

Containment

  1. Immediately revoke all active sessions for the compromised account using the Microsoft Entra admin center (Users → Sign-in logs → Revoke sessions) or via PowerShell: Revoke-AzureADUserAllRefreshToken -ObjectId <UPN>.
  2. Force a password reset via Azure AD admin portal and require re-registration of MFA methods. If the account has Global Administrator or privileged roles, escalate immediately and consider temporary account disable pending investigation.
  3. Apply a Conditional Access policy to restrict the account to compliant, Intune-managed devices from trusted named locations only, blocking all other access pending investigation completion.
  4. If the sign-in originated from a bulletproof ASN or Tor exit node, create a named location block in Conditional Access for that IP range and add the specific IPs to the Microsoft Defender Threat Intelligence block list.
  5. Search for OAuth applications the user may have consented to during the compromised session (AuditLogs | where OperationName == 'Consent to application') and revoke any suspicious app permissions immediately.

Evidence Collection

  1. Export the complete AADSignInLogs for the user covering T-72h to T+24h relative to the first suspicious event, including all fields: IPAddress, Location, DeviceDetail, AuthenticationDetails, ConditionalAccessPolicies, RiskDetail.
  2. Collect AuditLogs for the same window filtered to the user's ObjectId covering: password changes, MFA method changes, role assignments, group membership changes, OAuth consent events, and named location modifications.
  3. If the compromised account has Exchange Online access, collect OfficeActivity records for MailItemsAccessed, Send, and FolderBind operations to determine whether the adversary read or exfiltrated email content.
  4. Capture the DeviceDetail JSON from the suspicious successful sign-in (browser, OS, device ID) to fingerprint the adversary's access device for correlation across other potentially compromised accounts.
  5. Pull CloudAppEvents for any file access or download activity in SharePoint, OneDrive, or Teams during the compromised session window. Note file names, sizes, and destination IPs for any download operations.

Escalation Criteria

  • ! Escalate to Incident Response immediately if the compromised account holds any privileged Azure AD roles (Global Admin, Privileged Role Administrator, Security Administrator, Exchange Administrator) — blast radius is organizational.
  • ! Escalate if evidence of email access or exfiltration is found (MailItemsAccessed events) involving HR, Finance, Legal, or Executive mailboxes, as this likely constitutes a data breach requiring legal and compliance notification.
  • ! Escalate if the same source IP or user agent pattern is found in AADSignInLogs for multiple other accounts, indicating a coordinated credential stuffing campaign targeting the organization.
  • ! Escalate if MFA re-registration or trusted device additions occurred under the compromised session, confirming the adversary has established durable access and is attempting to survive a password reset.
  • ! Escalate if OfficeActivity shows the compromised account sent internal emails with attachments or links (potential internal spearphishing — T1534) during the suspicious session window.

Investigation Guide

Forensic Artifacts

  • > Azure AD Sign-in Logs (AADSignInLogs) — full authentication record including IP, user agent, device, MFA method, conditional access outcome, and risk score
  • > Azure AD Audit Logs (AuditLogs) — MFA method registration and deletion, password reset events, OAuth consent grants, role assignment changes
  • > OfficeActivity table — MailItemsAccessed, FolderBind, Send, FileDownloaded events linked to the compromised session
  • > Windows Security EventID 4624/4625/4648 — local and network authentication successes and failures on-premises with source IP and logon type
  • > Windows Security EventID 4776 — NTLM credential validation attempts against domain controllers, useful for identifying password spray patterns
  • > Conditional Access sign-in diagnostic reports — show which policies evaluated and whether enforcement gaps allowed the compromised session through
  • > Microsoft Entra ID Identity Protection RiskDetections — automated risk signals including leaked credentials, impossible travel, anonymous IP use, and password spray detections from Microsoft telemetry

Tuning Guidance

Start by establishing baselines: run the KQL credential stuffing query with FailureCount >= 20 for the first week to identify high-volume patterns, then tune down to 5. Add known corporate VPN egress IPs and branch office subnets to Azure AD Named Locations and exclude them from the SuspiciousASN detection. For impossible travel, configure the RiskDetections table through Identity Protection rather than rebuilding the logic — Microsoft's signal has lower false positive rates than time-based heuristics. Create watchlists in Microsoft Sentinel for legitimate shared accounts (service desks, shared mailboxes) and exclude them from CredentialStuffing detections. Suppress the legacy auth hunt for any service accounts explicitly exempted from the legacy auth Conditional Access policy. Correlate detections with Identity Protection RiskLevel before paging — low risk scores combined with high failure thresholds are often automation or misconfigured clients.


Hunting Queries

Hunts for accounts flagged by Azure AD Identity Protection as having leaked or sprayed credentials that also had recent successful authentications, indicating active exploitation of known-compromised credentials. Complements the main detection by using Microsoft's breach correlation telemetry rather than behavioral thresholds.

Hunting — KQL
kql
// Hunt: Accounts with successful login after credential exposure in breach databases
// Cross-references Identity Protection leaked credential detections with successful sign-ins
AADRiskyUsers
| where RiskDetail has_any ("leakedCredentials", "passwordSpray", "anonymizedIPAddress")
| where RiskState in ("atRisk", "confirmedCompromised")
| join kind=inner (
    AADSignInLogs
    | where TimeGenerated > ago(30d)
    | where ResultType == "0"
    | summarize LastSuccessfulLogin = max(TimeGenerated), LoginCount = count(),
        SuccessIPs = make_set(IPAddress, 10), Apps = make_set(AppDisplayName, 5)
        by UserPrincipalName
) on UserPrincipalName
| project UserPrincipalName, RiskDetail, RiskState, RiskLastUpdatedDateTime,
    LastSuccessfulLogin, LoginCount, SuccessIPs, Apps
| sort by RiskLastUpdatedDateTime desc
Hunting — SPL
spl
index=* sourcetype="WinEventLog:Security" EventCode=4625
| eval FailHour=strftime(_time, "%Y-%m-%d %H:00")
| stats dc(src_ip) AS UniqueSourceIPs, count AS FailCount
    by user, FailHour
| where UniqueSourceIPs > 20 AND FailCount > 100
| eval DetectionType="PasswordSprayCampaign"
| sort - FailCount

Hunts for OAuth application consent grants that occurred within 2 hours of a high-risk sign-in event, a pattern consistent with adversaries establishing persistent delegated access to mailboxes and files after initial account compromise. This is distinct from the main detection's authentication anomaly focus.

Hunting — KQL
kql
// Hunt: New OAuth application consents during high-risk sign-in sessions
// Adversaries often consent OAuth apps post-compromise to establish persistent access
let RiskySignIns = AADSignInLogs
| where TimeGenerated > ago(7d)
| where RiskLevelDuringSignIn in ("high", "medium")
| where ResultType == "0"
| project UserId = UserId, RiskySignInTime = TimeGenerated, IPAddress, Location;
AuditLogs
| where TimeGenerated > ago(7d)
| where OperationName == "Consent to application"
| extend UserId = tostring(InitiatedBy.user.id)
| join kind=inner RiskySignIns on UserId
| where TimeGenerated between (RiskySignInTime .. (RiskySignInTime + 2h))
| extend AppName = tostring(TargetResources[0].displayName),
    ConsentedScopes = tostring(TargetResources[0].modifiedProperties)
| project TimeGenerated, UserId, AppName, ConsentedScopes, RiskySignInTime, IPAddress, Location
| sort by TimeGenerated desc
Hunting — SPL
spl
index=* sourcetype="WinEventLog:Security" EventCode IN (4624, 4648)
    LogonType IN (3, 10)
| eval Hour=strftime(_time, "%Y-%m-%d %H")
| stats dc(src_ip) AS UniqueSourceIPs, values(src_ip) AS SourceIPs,
    count AS LogonCount, values(WorkstationName) AS Sources
    by user, Hour
| where UniqueSourceIPs >= 3 AND LogonCount >= 5
| eval SuspiciousLateralMovement=if(UniqueSourceIPs > 5, "High", "Medium")
| table user, Hour, UniqueSourceIPs, LogonCount, SourceIPs, Sources, SuspiciousLateralMovement
| sort - UniqueSourceIPs

Hunts for accounts successfully authenticating via legacy protocols (IMAP, POP3, SMTP AUTH, ActiveSync) that bypass MFA enforcement, a common technique used after account credential compromise to maintain persistent access even when the password is reset and MFA is enforced on modern auth flows.

Hunting — KQL
kql
// Hunt: Accounts authenticating with legacy protocols after MFA enforcement
// Adversaries use legacy auth protocols (SMTP AUTH, IMAP, POP3) that bypass MFA
AADSignInLogs
| where TimeGenerated > ago(14d)
| where ClientAppUsed in (
    "Exchange ActiveSync", "IMAP", "POP3", "SMTP", "Other clients",
    "Authenticated SMTP", "Autodiscover", "Exchange Online PowerShell",
    "Exchange Web Services", "MAPI Over HTTP"
  )
| where ResultType == "0"
| where AuthenticationRequirement == "singleFactorAuthentication"
| summarize
    LegacyAuthSuccesses = count(),
    UniqueIPs = dcount(IPAddress),
    IPList = make_set(IPAddress, 5),
    Protocols = make_set(ClientAppUsed, 5),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated)
    by UserPrincipalName
| where LegacyAuthSuccesses > 0
| sort by LegacyAuthSuccesses desc
Hunting — SPL
spl
index=* sourcetype="WinEventLog:Security" EventCode=4624 LogonType=8
| stats count AS NTLMBasicCount, dc(src_ip) AS UniqueIPs, values(src_ip) AS SourceIPs,
    min(_time) AS FirstSeen, max(_time) AS LastSeen
    by user, TargetDomainName
| where NTLMBasicCount > 3
| eval Note="NTLM LogonType 8 = NetworkCleartext, indicates legacy auth or basic auth bypass"
| sort - NTLMBasicCount

Atomic Red Team Tests

Test 1 Simulate credential stuffing authentication pattern using PowerShell against Azure AD
windows

Generates multiple failed authentication attempts followed by a successful login against Azure AD using the Microsoft Authentication Library, simulating the pattern produced by credential stuffing tools. Requires a test account with a known password. WARNING: Run only in a test tenant or with explicit SOC coordination.

Command

powershell
# Prerequisites: Install MSAL.PS module
Install-Module -Name MSAL.PS -Force -Scope CurrentUser

$TenantId = "YOUR_TENANT_ID"
$ClientId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"  # Microsoft Azure CLI (public client)
$TestUPN = "[email protected]"
$WrongPassword = "WrongPassword123!"
$CorrectPassword = "ActualTestPassword1!"

# Generate 6 failure events
1..6 | ForEach-Object {
    try {
        Get-MsalToken -TenantId $TenantId -ClientId $ClientId `
            -UserCredential (New-Object PSCredential($TestUPN, `
            (ConvertTo-SecureString $WrongPassword -AsPlainText -Force))) `
            -ErrorAction Stop
    } catch { Write-Host "Failure $_: $_" }
    Start-Sleep -Seconds 2
}

# Generate 1 success event
Get-MsalToken -TenantId $TenantId -ClientId $ClientId `
    -UserCredential (New-Object PSCredential($TestUPN, `
    (ConvertTo-SecureString $CorrectPassword -AsPlainText -Force)))

Write-Host "Test complete. Verify AADSignInLogs for $TestUPN shows 6 failures then 1 success."

Cleanup

powershell
Remove-Module MSAL.PS -Force

Expected Telemetry

AADSignInLogs will show 6 ResultType != 0 events followed by 1 ResultType == 0 event for the test UPN from the same source IP within a short time window. RiskState may update in AADRiskyUsers within 15-30 minutes.

Expected Detection

KQL CredentialStuffing signal should fire with FailureCount=6, SuccessCount=1, UniqueIPs=1, RiskScore=55. SPL query should produce a matching row for the test account.

Test 2 Simulate legacy protocol authentication bypass against Exchange Online (SMTP AUTH)
windows

Tests SMTP AUTH authentication against Exchange Online using a test account, simulating how adversaries use legacy protocols to bypass MFA and access compromised email accounts. Validates the legacy protocol hunting query.

Command

powershell
# Test SMTP AUTH authentication (simulates adversary using compromised creds via legacy protocol)
$SMTPServer = "smtp.office365.com"
$SMTPPort = 587
$TestEmail = "[email protected]"
$TestPassword = "ActualTestPassword1!"

$SMTP = New-Object System.Net.Mail.SmtpClient($SMTPServer, $SMTPPort)
$SMTP.EnableSsl = $true
$SMTP.Credentials = New-Object System.Net.NetworkCredential($TestEmail, $TestPassword)

try {
    # Authenticate without sending mail — just test credentials
    $SMTP.Send($TestEmail, $TestEmail, "[ATOMIC TEST] Legacy Auth Detection Validation",
        "This is an atomic test for T1586 legacy auth detection. Sent: $(Get-Date)")
    Write-Host "SMTP AUTH succeeded — check AADSignInLogs for ClientAppUsed=Authenticated SMTP"
} catch {
    Write-Host "SMTP AUTH test result: $_"
} finally {
    $SMTP.Dispose()
}

Cleanup

powershell
No cleanup required. Delete the test email from the mailbox if sent successfully.

Expected Telemetry

AADSignInLogs entry with ClientAppUsed='Authenticated SMTP', AuthenticationRequirement='singleFactorAuthentication', ResultType=0 for the test account. This event will NOT appear in modern auth logs, validating the legacy auth gap.

Expected Detection

KQL legacy protocol hunting query should return the test UPN with LegacyAuthSuccesses >= 1 and protocol 'Authenticated SMTP' in Protocols list.

Test 3 Simulate account compromise indicators via failed then successful Windows network logon from multiple sources
windows

Uses net use commands from different simulated source contexts to generate Windows Security Event IDs 4625 (failure) and 4624 (success) for a test account, validating the SPL credential stuffing detection on Windows domain infrastructure.

Command

powershell
# Run from a domain-joined Windows host as admin
# Replace TEST_DC with your domain controller hostname and TESTDOMAIN\testuser with a valid test account

$DC = "TEST_DC"
$TestAccount = "TESTDOMAIN\\testuser_atomic"
$WrongPass = "BadPassword999!"
$CorrectPass = "CorrectPassword1!"

# Generate failure events (EventID 4625)
Write-Host "Generating authentication failures..."
1..6 | ForEach-Object {
    $result = & cmdkey /add:$DC /user:$TestAccount /pass:$WrongPass 2>&1
    & net use \\$DC\ADMIN$ /user:$TestAccount $WrongPass 2>&1 | Out-Null
    & net use \\$DC\ADMIN$ /delete 2>&1 | Out-Null
    Start-Sleep -Milliseconds 500
}

# Generate success event (EventID 4624 Type 3)
Write-Host "Generating successful authentication..."
& net use \\$DC\ADMIN$ /user:$TestAccount $CorrectPass
Start-Sleep -Seconds 2
& net use \\$DC\ADMIN$ /delete

Write-Host "Atomic test complete. Check Security event log on $DC for EventID 4625 x6 and 4624 x1 for $TestAccount"

Cleanup

powershell
net use * /delete /yes
cmdkey /delete:TEST_DC

Expected Telemetry

Windows Security EventID 4625 (LogonType 3, SubStatus 0xC000006A = wrong password) six times followed by EventID 4624 (LogonType 3) once for the test account in the domain controller Security event log. Source workstation will be the executing host.

Expected Detection

SPL query should return a row for testuser_atomic with FailureCount=6, SuccessCount=1, UniqueIPs=1 (or more if run from multiple hosts), RiskScore=55, DetectionType=CredentialStuffingThenSuccess.

Related Detections