T1110.004

Credential Stuffing

Adversaries may use credentials obtained from breach dumps of unrelated accounts to gain access to target accounts through credential overlap. Unlike password spraying (T1110.003), which tests one password against many accounts, credential stuffing uses known username-password pairs harvested from prior data breaches — exploiting users who reuse passwords across personal and business accounts. Targeted services commonly include SSH (22/TCP), RDP (3389/TCP), SMB (445/TCP), LDAP (389/TCP), HTTP management portals, VPN gateways, and cloud identity providers such as Azure AD, Okta, and federated SSO endpoints. Real-world threat actors including Chimera and TrickBot (rdpscanDll module) have used credential stuffing at scale against enterprise remote services.

Microsoft Sentinel / Defender
kusto
let LookbackWindow = 1h;
let FailureThreshold = 15;
let AccountThreshold = 5;
// Windows Security Event failed network/remote-interactive logons
let WindowsStuffing = SecurityEvent
| where TimeGenerated > ago(LookbackWindow)
| where EventID == 4625
| where LogonType in (3, 10)
| where isnotempty(IpAddress) and IpAddress != "-" and IpAddress != "127.0.0.1" and IpAddress != "::1"
| summarize
    FailureCount = count(),
    UniqueTargetAccounts = dcount(TargetUserName),
    AccountSample = make_set(TargetUserName, 10),
    TargetHosts = make_set(Computer, 5),
    SubStatusCodes = make_set(SubStatus, 5),
    FirstAttempt = min(TimeGenerated),
    LastAttempt = max(TimeGenerated)
    by SourceIP = IpAddress, WorkstationName
| where FailureCount >= FailureThreshold and UniqueTargetAccounts >= AccountThreshold;
// Azure AD sign-in failures (cloud credential stuffing)
let AzureStuffing = SigninLogs
| where TimeGenerated > ago(LookbackWindow)
| where ResultType in ("50126", "50053", "50055", "50056", "50057", "50034", "70008", "81016")
| where isnotempty(IPAddress)
| summarize
    FailureCount = count(),
    UniqueTargetAccounts = dcount(UserPrincipalName),
    AccountSample = make_set(UserPrincipalName, 10),
    AppSample = make_set(AppDisplayName, 5),
    UserAgents = make_set(UserAgent, 5),
    FirstAttempt = min(TimeGenerated),
    LastAttempt = max(TimeGenerated)
    by SourceIP = IPAddress
| where FailureCount >= FailureThreshold and UniqueTargetAccounts >= AccountThreshold;
// Successful logon from same source IP — critical indicator that a stuffed pair succeeded
let SuccessAfterFailure = SecurityEvent
| where TimeGenerated > ago(LookbackWindow)
| where EventID == 4624
| where LogonType in (3, 10)
| where isnotempty(IpAddress) and IpAddress != "-"
| summarize
    SuccessCount = count(),
    SuccessAccounts = make_set(TargetUserName, 10)
    by SuccessSourceIP = IpAddress;
union
  (WindowsStuffing
   | join kind=leftouter SuccessAfterFailure on $left.SourceIP == $right.SuccessSourceIP
   | extend
       DataSource = "Windows Security Events",
       StuffingSuccess = isnotempty(SuccessCount),
       Severity = iff(isnotempty(SuccessCount), "Critical", "High")
   | project
       TimeGenerated = LastAttempt, SourceIP, DataSource, FailureCount,
       UniqueTargetAccounts, AccountSample, TargetHosts, SubStatusCodes,
       FirstAttempt, LastAttempt, StuffingSuccess, SuccessAccounts, Severity),
  (AzureStuffing
   | extend
       DataSource = "Azure AD SigninLogs",
       StuffingSuccess = false,
       Severity = "High",
       TargetHosts = dynamic([]),
       SubStatusCodes = dynamic([]),
       SuccessAccounts = dynamic([])
   | project
       TimeGenerated = LastAttempt, SourceIP, DataSource, FailureCount,
       UniqueTargetAccounts, AccountSample, TargetHosts, SubStatusCodes,
       FirstAttempt, LastAttempt, StuffingSuccess, SuccessAccounts, Severity)
| sort by StuffingSuccess desc, FailureCount desc
high severity high confidence

Data Sources

Logon Session: Logon Session Creation Logon Session: Logon Session Metadata Application Log: Application Log Content Windows Security Event ID 4625 Windows Security Event ID 4624 Azure AD SigninLogs

Required Tables

SecurityEvent SigninLogs

False Positives

  • Misconfigured service accounts with expired or incorrect credentials repeatedly attempting authentication against multiple systems simultaneously — will appear as high-volume failures from a single host IP
  • Azure AD Connect or third-party directory sync tools (Okta, OneLogin) generating batched authentication failures during sync interruptions or object mismatches
  • Authenticated security scanners (Nessus, Qualys, Rapid7) running credential-based assessments from a dedicated scan IP against multiple systems
  • Load balancers, NAT gateways, or reverse proxies that masquerade multiple users behind a single egress IP, making unrelated individual user failures aggregate as a single-IP attack
  • Helpdesk or IT staff testing account lockout/reset workflows by deliberately triggering failures across multiple test accounts from their workstation

Unlock Pro Content

Get the full detection package for T1110.004 including response playbook, investigation guide, and atomic red team tests.

Response PlaybookInvestigation GuideHunting QueriesAtomic Red Team TestsTuning Guidance

Related Detections