NIST SP 800-53 Rev. 5 · Access Control

AC-7 on Microsoft Sentinel

Repeated failed sign-ins without lockout — a published KQL query you can run against Microsoft Sentinel / Log Analytics, with the evidence it needs, the settings you decide, and what a result does and does not establish.

Why this matters

An account under password-guessing attack that was never locked

Locking an account after a number of failed sign-in attempts is one of the oldest protections in computing. When it is misconfigured — which is common — an attacker can guess passwords indefinitely. Nothing visibly breaks, so the gap can persist for years.

Why this check earns its place. The value is in the distinction the check makes. Reporting every attacked account would be useless noise; this reports only accounts where the protection failed to engage.

What it reads
The organization's own sign-in records.
What it reports
Accounts that passed the organization's own limit on failed attempts without the system locking them.

See related public disclosures on the Signals page →

ControlAC-7 — Unsuccessful Logon Attempts
PlatformMicrosoft Sentinel (KQL)
SeverityHigh
ValidationNot tenant-validated

What this finds

An account exceeded the defined consecutive-failure threshold within the counting window, and no lockout was recorded during the burst or in the hour following it.

The requirement

The organization must enforce a limit of consecutive invalid logon attempts by a user during an organization-defined time period, and automatically lock the account, delay the next prompt, or take another organization-defined action when that limit is exceeded.

NIST SP 800-53 Rev. 5 — AC-7 (Unsuccessful Logon Attempts).

NIST requires that a limit exist and that exceeding it triggers a response. The attempt threshold, the counting window, and what happens on breach are all organization-defined — NIST prescribes no numbers. This pack reports accounts whose failures passed your stated threshold with no lockout recorded afterwards, which evidences whether the limit you defined is actually being enforced.

How this maps to the CISA performance goals

The Cross-Sector Cybersecurity Performance Goals are a voluntary baseline published by CISA — the U.S. federal agency for critical-infrastructure cybersecurity — written for organizations too small to employ security staff. This control speaks to:

  • 3.E — Monitor unsuccessful (automated) login attempts
    The goal is monitoring unsuccessful login attempts; the pack finds accounts that passed the defined attempt limit with no lockout recorded while it happened.
  • 3.B — Establish minimum password strength (partial — see note)
    An unenforced attempt limit weakens whatever password strength is set, but the pack checks lockout enforcement rather than the strength policy itself.

See the full coverage map, including the goals no query can answer →

Evidence this query needs

None. This query runs on platform telemetry alone — there is no file to prepare and nothing to export.

The query

Published as ac-7-failed-signins.azure.kql. Reads SigninLogs (Microsoft Entra ID). Requires: Log Analytics Reader.

Before this returns anything, SigninLogs (Microsoft Entra ID) has to be reaching Microsoft Sentinel / Log Analytics. If not, the query reports nothing found — which looks exactly like nothing wrong. What has to be switched on first →

// AC-7 -- accounts past the failure threshold with no lockout recorded
// Platform telemetry only. No supplied evidence file is required.
let Window = 1h;
let Lookback = 7d;
let Threshold = 10;
// A lockout is emitted AFTER the attempt that trips the limit, so it lands
// after the last failure, not between the first and the last. Requiring it to
// fall inside the failure span reported correctly locked-out accounts as
// findings. This grace admits the lockout that follows the burst.
let LockoutGrace = 1h;
// ResultType 50053 is the Entra ID "account locked" outcome. Its presence is
// how this query evidences that the limit was actually enforced -- absence of
// the lockout, not absence of failures, is the finding.
let Lockouts =
    SigninLogs
    | where TimeGenerated > ago(Lookback)
    | where ResultType == "50053"
    | project LockedUser = tolower(UserPrincipalName), LockoutTime = TimeGenerated;
SigninLogs
| where TimeGenerated > ago(Lookback)
| where ResultType != "0"
// Service principals authenticate differently and are excluded by parameter
| where isnotempty(UserPrincipalName)
| summarize FailureCount = count(),
            DistinctIPs   = dcount(IPAddress),
            FirstFailure  = min(TimeGenerated),
            LastFailure   = max(TimeGenerated)
        by UserKey = tolower(UserPrincipalName), Bin = bin(TimeGenerated, Window)
| where FailureCount >= Threshold
| join kind=leftouter (Lockouts) on $left.UserKey == $right.LockedUser
// A lockout during or just after the burst is the control working, not a finding
| where isnull(LockoutTime) or LockoutTime < FirstFailure or LockoutTime > LastFailure + LockoutGrace
| extend LockoutObserved = isnotnull(LockoutTime)
| project UserKey, Bin, FailureCount, DistinctIPs,
          FirstFailure, LastFailure, LockoutObserved
| order by FailureCount desc

Open this pack on Microsoft Sentinel to adjust the settings and download it →

The interactive version opens on Microsoft Sentinel — the platform on this page — even if your environment profile does not list it.

Settings you decide

NIST states the objective and leaves these to your organization. The interactive version of this pack applies them to the query for you.

Consecutive failure threshold (default 10)
How many failed sign-ins inside the counting window before the account is reported. NIST prescribes no number — this is your organization-defined limit.
Counting window (hours) (default 1)
The period over which failures are counted before the threshold applies.
Limit to specific accounts (optional)
Paste user principal names, one per line.

Running it continuously

Sentinel scheduled analytics rule

Rule type:        Scheduled query rule
Run frequency:    Every 1 hour
Lookup period:    Last 24 hours
Trigger:          Number of query results > 0
Entity mapping:   Account -> UserKey, IP -> IPAddress
Severity:         High
MITRE tactic:     Credential Access (T1110 Brute Force)
Incident:         Create incident, group alerts by Account entity
Suppression:      4 hours per matched account

Suppression is shorter here than on the lifecycle packs: a brute-force pattern that resumes after four hours is new information, not a repeat of the same unresolved finding.

What a result does not prove

Failed sign-ins do not establish that an attack occurred or that any account was compromised. They establish that the organization's own attempt limit was not evidenced as enforced for that account in that window. A stuck client, an expired cached credential, or a misconfigured service can produce the same pattern.

Validation status for this platform: Not tenant-validated. Structure, query generation, and preflight are covered by the project's automated test suites. This pack has not been executed against a live tenant, account, or index. Validate it in a non-production scope before relying on any result.

Before you act on a result

  • Is the lockout policy actually configured for this account population, or only for a subset?
  • Do the failures come from one source address, which suggests a stuck client rather than an attack?
  • Is a service account authenticating interactively and failing on a rotated secret?
  • Was the account locked by a system this query does not read, such as an on-premises domain controller?
  • Does a conditional-access or risk-based policy already block these attempts before lockout would apply?

Common questions

How do I check for repeated failed sign-ins without lockout in Microsoft Sentinel?
Run the published KQL query on this page against Microsoft Sentinel / Log Analytics. It reads SigninLogs (Microsoft Entra ID) and reports: An account exceeded the defined consecutive-failure threshold within the counting window, and no lockout was recorded during the burst or in the hour following it.
What permissions are needed to run this Microsoft Sentinel query?
Log Analytics Reader.
Does a result from this query mean the control has failed?
Failed sign-ins do not establish that an attack occurred or that any account was compromised. They establish that the organization's own attempt limit was not evidenced as enforced for that account in that window. A stuck client, an expired cached credential, or a misconfigured service can produce the same pattern.

Terms used on this page

NIST SP 800-53the U.S. federal catalogue of security requirements
The catalogue of security and privacy requirements published by the National Institute of Standards and Technology, a U.S. federal agency. U.S. government systems are measured against it, and many private organizations adopt it voluntarily. Each requirement has an identifier such as AC-2. Official source →
CISACybersecurity and Infrastructure Security Agency
The U.S. federal agency responsible for national critical-infrastructure cybersecurity. Its publications are works of the U.S. government and are free to use. Official source →
KQLKusto Query Language
The language used to ask questions of data held in Microsoft Sentinel. A query written in it is text, like a spreadsheet formula, and can be read before it is run.
CPGCross-Sector Cybersecurity Performance Goals
A voluntary baseline of security practices published by the U.S. Cybersecurity and Infrastructure Security Agency, written specifically for organizations too small to employ security staff. It is a short, plainly written starting list rather than a full standard. Official source →
not tenant-validatednever executed against real data
The check has passed the project's automated tests for structure and syntax, but has never been run against real data anywhere. Test it in a safe scope before relying on any result.

Every term this project uses →

The same control on other platforms