Web Security

Account Takeover Prevention: Detecting and Stopping ATO Attacks

A deep technical look at the account takeover attack chain, the signals that reveal credential abuse in real time, and the layered defenses—risk-based auth, step-up MFA, and HaveIBeenPwned integration—that stop attackers before damage is done.

August 1, 20259 min readShipSafer Team

What Is Account Takeover?

Account takeover (ATO) is the process by which an attacker gains unauthorized control of a legitimate user's account. Unlike a brute-force attack that targets a single account with random guesses, modern ATO is an industrial operation. Attackers buy breach databases, automate credential testing at massive scale, and monetize access within hours. The victim never sees a suspicious email, never clicks a phishing link—their password simply worked on your platform because they reused it from a site that was breached three years ago.

The scale is staggering. SpyCloud's 2024 Annual Identity Exposure Report found over 1.7 billion recaptured credentials from breach data published in the prior year. The economics are simple: a list of 10 million credentials costs tens of dollars on dark web markets, and even a 0.1% success rate yields 10,000 compromised accounts.

The ATO Attack Chain

Understanding the full chain helps identify where defenses are most effective.

Stage 1: Credential acquisition. Attackers obtain combo lists—username:password pairs—from dark web markets, Telegram channels, or their own prior breaches. Lists are often deduplicated and normalized for a specific target vertical (e.g., finance credentials separated from gaming credentials).

Stage 2: Credential testing (stuffing). Automated tools like Sentry MBA, STORM, or custom Python scripts replay credentials against login endpoints. Sophisticated actors use residential proxy networks to distribute requests across millions of IP addresses, defeating IP-based rate limiting. They solve CAPTCHAs using third-party farms or ML-based solvers.

Stage 3: Account validation. Successful logins are sorted by account value. Tools check for saved payment methods, loyalty points, stored personal information, and active subscriptions. High-value accounts are flagged for immediate exploitation.

Stage 4: Monetization. Attackers cash out through gift card fraud, unauthorized purchases, account resale, data harvesting, or lateral phishing—using your platform to target the compromised user's contacts.

Stage 5: Persistence. Before moving on, attackers often change the recovery email, disable MFA, or add a fraudulent payment method to maintain access even after a forced password reset.

Detection Signals

The best time to stop ATO is during Stage 2—credential testing—before any account is actually compromised. These signals, used in combination, provide high-fidelity detection.

Impossible Travel

If a user logs in from New York at 09:00 and from Tokyo at 09:15, at least one of those logins is suspicious. The "impossible" threshold should account for high-speed travel (flights) but flag logins where the physical distance divided by the time delta exceeds realistic travel speed—roughly 900 km/h for commercial aviation.

Implementation: store the last successful login IP and timestamp. On each new login, geolocate the new IP (MaxMind GeoLite2 works well), compute distance and velocity, and flag accordingly.

from geopy.distance import geodesic
from datetime import datetime

def is_impossible_travel(last_ip_geo, current_ip_geo, last_login_time, current_time):
    distance_km = geodesic(last_ip_geo, current_ip_geo).km
    elapsed_hours = (current_time - last_login_time).total_seconds() / 3600
    if elapsed_hours < 0.01:
        elapsed_hours = 0.01
    speed_kmh = distance_km / elapsed_hours
    return speed_kmh > 900  # faster than commercial aviation

New Device Fingerprint

Device fingerprinting combines browser characteristics (user agent, screen resolution, canvas fingerprint, installed fonts, WebGL renderer) into a stable identifier. A login from a fingerprint never seen for that account is a risk signal, not a block trigger—many legitimate users switch devices.

Libraries like FingerprintJS (open source) or commercial alternatives like Castle, DataDome, or Stytch provide fingerprinting as a service. Store the set of known device fingerprints per user. Assign a risk score bump for unfamiliar fingerprints.

Login Velocity and Distributed Attacks

A single IP sending 500 login attempts per hour is obvious. A botnet using 50,000 IPs each sending one attempt per day is much harder to detect at the IP level. Instead, monitor at the credential level: how many distinct source IPs attempted login for a given email in the last 24 hours? More than 3–5 distinct IPs for one account is a strong signal.

Also monitor at the platform level: what is the overall login failure rate? A spike from 2% to 15% failure rate is a stuffing attack signature even if no single account stands out.

ASN and Hosting Provider Risk

Legitimate users rarely log in from datacenter IP ranges (AWS, DigitalOcean, Vultr). Residential proxies are harder to detect but have known ASNs. Services like IPQualityScore and Spur provide proxy/VPN/datacenter classification. A login from a datacenter IP should trigger additional verification, not necessarily a block.

Behavioral Biometrics

Advanced systems analyze typing rhythm (dwell time, flight time between keys), mouse movement patterns, and scroll behavior. ML models trained on per-user baselines can flag logins where the behavioral pattern doesn't match the user's history. This is expensive to build in-house but available via vendors like BioCatch and TypingDNA.

Risk-Based Authentication

Rather than applying uniform friction to all logins, risk-based authentication (RBA) assigns a risk score to each login attempt and adjusts the authentication requirement accordingly.

A simple scoring model:

SignalRisk Points
New device fingerprint+20
Datacenter IP+25
Impossible travel+40
Multiple IPs for same email (last 24h)+30
Password in breach database+35
Login from new country+15

Risk score thresholds:

  • 0–30: Allow login normally
  • 31–60: Require step-up MFA
  • 61–80: Require step-up MFA + notify user by email
  • 81+: Block and require account recovery flow

The thresholds are starting points—tune them against your own false positive rates. A user who logs in from a new country on a new device while traveling legitimately shouldn't be permanently locked out.

Step-Up MFA

Step-up MFA is the practice of requiring a second factor only when risk is elevated, rather than on every login. This reduces friction for normal users while maintaining strong protection when signals are suspicious.

The step-up should be resistant to the attack type. For credential stuffing, the attacker has the password but likely does not have access to:

  • A TOTP app (Google Authenticator, Authy)
  • A hardware key (YubiKey, FIDO2)
  • The user's verified phone number (SMS OTP—weaker due to SIM swapping but still effective against stuffing)
  • The user's registered email if the attacker has not yet changed it

Avoid SMS as the sole step-up factor for high-value accounts. Implement TOTP via a library like speakeasy (Node.js) or pyotp (Python). For the highest-security accounts, push WebAuthn/FIDO2 as the step-up method—it is phishing-resistant and requires physical possession of the device.

After a suspicious login is verified via step-up, log the new device/IP as trusted so the user is not repeatedly challenged on subsequent logins from the same context.

HaveIBeenPwned API Integration

Troy Hunt's HaveIBeenPwned (HIBP) service exposes an API to check whether a password appears in known breach databases. The k-anonymity model means you never send the full password hash to the API.

How it works:

  1. Hash the password with SHA-1
  2. Send the first 5 characters of the hex digest to the API: GET https://api.pwnedpasswords.com/range/{first5chars}
  3. The API returns all hash suffixes that match that prefix, along with breach counts
  4. Check whether your full hash's suffix appears in the response
async function isPasswordPwned(password) {
  const crypto = require('crypto');
  const hash = crypto.createHash('sha1').update(password).digest('hex').toUpperCase();
  const prefix = hash.slice(0, 5);
  const suffix = hash.slice(5);

  const response = await fetch(`https://api.pwnedpasswords.com/range/${prefix}`);
  const text = await response.text();

  const lines = text.split('\r\n');
  for (const line of lines) {
    const [hashSuffix, count] = line.split(':');
    if (hashSuffix === suffix) {
      return parseInt(count, 10);
    }
  }
  return 0;
}

Integrate this check at two points:

  1. At registration and password change: Block passwords that appear more than N times in breaches (a threshold of 10 is reasonable for sensitive applications; NIST SP 800-63B recommends blocking any known breached password).
  2. At login: For existing accounts, check the provided password against HIBP. If it appears in breaches, require the user to change their password before continuing, even if the current login is not otherwise suspicious.

The HIBP API has a generous rate limit for legitimate use. Cache results locally for 24 hours to reduce API calls at scale.

Notification and Recovery

When an ATO is suspected or confirmed:

  1. Immediately invalidate all active sessions except the one being used for verification (or all sessions if the attack is confirmed post-hoc).
  2. Email the account holder at the registered address. The email should not contain a magic link by default—if the attacker controls the email, that is exploitable. Instead, direct the user to your secure account recovery flow.
  3. Log a security event with full context (IPs, device fingerprints, actions taken during the session) for forensics.
  4. Review actions taken during the compromised session. Reverse fraudulent purchases, flag any data that was accessed for potential notification obligations, and check for persistence mechanisms the attacker may have installed (saved payment methods, API keys, OAuth grants).

Operational Monitoring

ATO defense requires ongoing tuning. Build a dashboard that tracks:

  • Login failure rate (overall and per endpoint)
  • Step-up MFA challenge rate
  • Accounts locked per hour
  • Source ASN distribution of failed logins
  • Impossible travel detections per hour

Set alerts on thresholds: if the login failure rate doubles over a 15-minute window, that warrants immediate investigation. Integrate with your SIEM (Splunk, Elastic, Datadog) to correlate with other signals.

Consider joining threat-sharing communities like the FS-ISAC (finance sector) or the Retail & Hospitality ISAC, which share IOCs including IP ranges and proxy ASNs actively used in ATO campaigns.

Beyond Passwords: The Long Game

The most durable defense against credential stuffing is eliminating shared secrets entirely. Passkeys (FIDO2/WebAuthn) replace passwords with device-bound cryptographic credentials. They are phishing-resistant, stuffing-resistant, and provide a better user experience than passwords plus TOTP.

If a full passkey migration is not feasible today, a practical roadmap is:

  1. Integrate HIBP checks to flush compromised passwords from your user base.
  2. Enforce MFA for all accounts with access to sensitive data or high-value actions.
  3. Implement risk-based authentication with step-up for suspicious logins.
  4. Add device fingerprinting and session binding.
  5. Offer passkeys as an option and incentivize adoption.
  6. Retire password login for privileged accounts entirely.

The attackers running credential stuffing operations are running businesses. Make your platform expensive enough to attack that they move on to easier targets.

account-takeover
ATO
credential-stuffing
MFA
authentication
risk-based-auth

Check Your Security Score — Free

See exactly how your domain scores on DMARC, TLS, HTTP headers, and 25+ other automated security checks in under 60 seconds.