NIST SP 800-53 Rev. 5 · Access Control
AC-7 on AWS
Repeated failed sign-ins without lockout — a published SQL (Athena) + Lambda query you can run against Amazon Athena over CloudTrail, 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 →
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.aws.sql. Reads cloudtrail_logs (Athena table over the CloudTrail S3 export). Requires: athena:StartQueryExecution; glue:GetTable; s3:GetObject.
Before this returns anything,
cloudtrail_logs (Athena table over the CloudTrail S3 export) has to be reaching Amazon Athena over CloudTrail. If not, the query reports nothing found — which looks
exactly like nothing wrong. What has to be switched on first →
-- AC-7 -- principals past the failure threshold with no lockout recorded
-- Platform telemetry only. No supplied evidence file is required.
-- IAM has no native console lockout, so "lockout observed" is evidenced by the
-- principal's access key or console access being disabled after the failures.
WITH failures AS (
SELECT
lower(coalesce(useridentity.username, useridentity.arn)) AS user_key,
date_trunc('hour', from_iso8601_timestamp(eventtime)) AS window_start,
count(*) AS failure_count,
count(DISTINCT sourceipaddress) AS distinct_ips,
min(from_iso8601_timestamp(eventtime)) AS first_failure,
max(from_iso8601_timestamp(eventtime)) AS last_failure
FROM cloudtrail_logs
WHERE eventname = 'ConsoleLogin'
AND responseelements LIKE '%Failure%'
AND from_iso8601_timestamp(eventtime) > current_timestamp - interval '7' day
GROUP BY 1, 2
),
lockouts AS (
SELECT
lower(coalesce(useridentity.username, useridentity.arn)) AS user_key,
from_iso8601_timestamp(eventtime) AS lockout_time
FROM cloudtrail_logs
WHERE eventname IN ('UpdateLoginProfile', 'DeleteLoginProfile', 'UpdateAccessKey')
AND from_iso8601_timestamp(eventtime) > current_timestamp - interval '7' day
)
SELECT
f.user_key,
f.window_start,
f.failure_count,
f.distinct_ips,
f.first_failure,
f.last_failure,
(l.user_key IS NOT NULL) AS lockout_observed
FROM failures f
LEFT JOIN lockouts l
ON f.user_key = l.user_key
-- The credential-disabling event follows the burst rather than falling inside
-- it, so the upper bound carries an hour of grace. Without it, an account that
-- WAS acted on is reported as one that was not.
AND l.lockout_time BETWEEN f.first_failure AND f.last_failure + interval '1' hour
WHERE f.failure_count >= 10
AND l.user_key IS NULL
ORDER BY f.failure_count DESC;
Open this pack on AWS to adjust the settings and download it →
The interactive version opens on AWS — 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 console logins inside the counting window before the principal is reported.
- Counting window (hours) (default 1)
- CloudTrail rows are bucketed by this window before the threshold is applied.
- Limit to specific principals (optional)
- Paste IAM user names, one per line.
Running it continuously
EventBridge Scheduler → Lambda → Security Hub
Schedule: EventBridge Scheduler, rate(1 hour)
Compute: Lambda (Python 3.12), 60s timeout
Step 1: Start Athena query, poll for completion
Step 2: Build an ASFF finding per returned row
Step 3: BatchImportFindings -> AWS Security Hub
Severity label: HIGH
Workflow status: NEW
RelatedRequirements: NIST.800-53.r5 AC-7
Idempotency: Id = sha256(user_key + window_start)
The window start is part of the finding Id so a sustained attack across several hours raises a finding per window rather than collapsing into one that looks resolved.
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 AWS?
- Run the published SQL (Athena) + Lambda query on this page against Amazon Athena over CloudTrail. It reads cloudtrail_logs (Athena table over the CloudTrail S3 export) 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 AWS query?
- athena:StartQueryExecution; glue:GetTable; s3:GetObject.
- 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 →
- 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.