Guides
Detection guide

Impossible Travel, Credential Stuffing and Brute Force Detection

By Sazabi Team

Last verified on Nov 8, 2026.

Login traffic holds several different attacks that look alike on a graph and need different responses. Brute force guesses many passwords for one account. Password spraying tries one or a few common passwords across many accounts. Credential stuffing replays username and password pairs stolen elsewhere. A hijacked account shows up as a successful login from somewhere it should not be.

OWASP describes credential stuffing as automated injection of stolen credential pairs into login forms, exploiting password reuse. It also calls it a subset of brute force. MITRE notes that password spraying exists specifically to avoid the lockouts that guessing against a single account would trigger. You need checks that group by more than one key.

What the evidence looks like

At the identity provider, the raw material is a stream of sign-in events. This example shows a failed AWS console sign-in in the form CloudTrail records, trimmed to the fields that matter:

{
  "eventName": "ConsoleLogin",
  "eventTime": "2026-11-08T03:12:41Z",
  "sourceIPAddress": "198.51.100.23",
  "userIdentity": { "type": "IAMUser", "userName": "alex" },
  "responseElements": { "ConsoleLogin": "Failure" },
  "additionalEventData": { "MFAUsed": "No" },
  "errorMessage": "Failed authentication"
}

Your own application logs need the same fields: timestamp, account or attempted username, source IP, network or country, result and a reason. A stuffing run looks like this over a few minutes:

2026-11-08T03:12:41Z login result=fail user=a.nguyen ip=198.51.100.23 asn=64500
2026-11-08T03:12:41Z login result=fail user=b.ortiz ip=203.0.113.88 asn=64501
2026-11-08T03:12:42Z login result=fail user=c.patel ip=192.0.2.140 asn=64502

Many different usernames, one attempt each, many addresses, all failing. No single account looks attacked.

How to detect it

  1. Log every attempt with the fields above, including attempts for accounts that do not exist.
  2. Run grouped queries on a short window. The first finds spraying and stuffing by network.
SELECT asn,
       count(*) AS attempts,
       count(DISTINCT username) AS accounts,
       round(100.0 * count(*) FILTER (WHERE result = 'fail') / count(*), 1) AS fail_pct
FROM login_events
WHERE ts > now() - interval '10 minutes'
GROUP BY asn
HAVING count(*) > 200 AND count(DISTINCT username) > 100 AND
       count(*) FILTER (WHERE result = 'fail') * 1.0 / count(*) > 0.9
ORDER BY attempts DESC;
  1. The second finds brute force against single accounts, and success that follows many failures.
SELECT username,
       count(*) FILTER (WHERE result = 'fail') AS fails,
       count(DISTINCT ip) AS source_ips,
       bool_or(result = 'success') AS got_in
FROM login_events
WHERE ts > now() - interval '15 minutes'
GROUP BY username
HAVING count(*) FILTER (WHERE result = 'fail') >= 10
ORDER BY fails DESC;
  1. The third finds impossible travel. For each successful login, compare it with the user's previous successful login, compute the distance and the implied speed and flag speeds no traveler could reach.
WITH s AS (
  SELECT username, ts, lat, lon, ip,
         lag(ts)  OVER w AS prev_ts,
         lag(lat) OVER w AS prev_lat,
         lag(lon) OVER w AS prev_lon
  FROM login_events
  WHERE result = 'success' AND ts > now() - interval '1 day'
  WINDOW w AS (PARTITION BY username ORDER BY ts)
), d AS (
  SELECT *,
         6371 * 2 * asin(sqrt(
           power(sin(radians(lat - prev_lat) / 2), 2) +
           cos(radians(prev_lat)) * cos(radians(lat)) *
           power(sin(radians(lon - prev_lon) / 2), 2))) AS km,
         greatest(extract(epoch FROM ts - prev_ts) / 3600, 0.1) AS hours
  FROM s
  WHERE prev_ts IS NOT NULL
)
SELECT username, ts, ip, round(km) AS km, round(hours::numeric, 1) AS hours
FROM d
WHERE km > 500 AND km / hours > 900
ORDER BY km DESC;

The 900 km/h limit is roughly airliner speed. It is a starting value, not a standard, so adjust it for your users.

Thresholds and pitfalls

Choose rates from your own login volume. A threshold of 200 attempts in 10 minutes means nothing for a site with 50 logins an hour, and far too little for one with 50,000. Take a typical failure rate per network and flag multiples of it, then add a distinct-account count so a busy office does not look like an attack.

Impossible travel is the noisiest of these. Microsoft documents that its detection suppresses false positives caused by VPNs and addresses from cloud providers that do not indicate a physical location, and that a new user has a seven-day learning period. Do the same: maintain a list of VPN and corporate ranges, require a minimum distance and skip first logins from a new account. Geolocation of mobile carriers can also be far from the user.

OWASP's cheat sheet warns that client-side defenses such as device fingerprinting can be spoofed, and suggests defenses should emit volume metrics filtered by IP. Treat your detection counts as a metric you review, not only an alert. Rotating addresses defeat per-IP rules, which is why the queries above group by network and by account.

What to do when it fires

For a stuffing or spraying run: rate-limit or challenge the offending network, then check which accounts logged in successfully during the run and force a password reset and session revocation for those. NIST SP 800-63B says verifiers shall implement a rate-limiting mechanism on failed attempts for an account, so confirm one exists. Require multi-factor authentication where you can.

For impossible travel: confirm with the user or check the device, then revoke sessions and tokens and review what the session did. The same account may show up in privilege changes or access anomalies shortly after.

Related guides: bots and scraping covers the automation that drives most stuffing, and rogue AI agents covers machine identities that act out of scope. For AWS sign-in events, see the AWS CloudTrail and AWS pages.

With Sazabi

Sazabi detects this today with no setup: the Sazabi agent automatically sets up sensors for your project and maintains them over time. Authentication Anomaly Alerts: Issues for Credential Stuffing and Account Takeover covers what it catches.

Frequently Asked Questions

What is impossible travel?

It is a pair of sign-ins or activities from places so far apart that the same person could not have traveled between them in the time that passed. It often means a second party is using the same credentials.

How is credential stuffing different from brute force?

OWASP calls credential stuffing a subset of brute force. Brute force guesses passwords. Credential stuffing replays stolen username and password pairs from other breaches and relies on password reuse.

How do I spot password spraying?

Look for one source or network trying the same few passwords against many accounts. Per-account lockout rules miss it because each account sees only a couple of attempts.

Why do I get false impossible-travel alerts?

VPNs, mobile carriers, corporate proxies and cloud provider addresses all put a user in a different place than they are. Suppress known ranges and require a minimum distance.

Sources

  1. Credential stuffing is the automated injection of stolen username and password pairs into login forms, it exploits password reuse and it is a subset of brute force attacks (accessed Nov 8, 2026)
  2. Client-side defenses such as device fingerprinting can be spoofed, defenses should emit volume metrics that can be filtered by IP address, and storing an account IP address history is suggested (accessed Nov 8, 2026)
  3. Password spraying tries one or a few common passwords against many accounts to avoid the lockouts that brute forcing one account would cause (accessed Nov 8, 2026)
  4. CloudTrail ConsoleLogin events record the outcome in responseElements, the source IP address and whether MFA was used (accessed Nov 8, 2026)
  5. Impossible travel is two activities from geographically distant locations in less time than travel would take; VPNs and cloud provider addresses cause false positives and there is a learning period of seven days for a new user (accessed Nov 8, 2026)
  6. NIST SP 800-63B says verifiers shall implement a rate-limiting mechanism that limits failed authentication attempts on an account (accessed Nov 8, 2026)