security auth_error ai_generated true

Login response time differs for valid vs invalid usernames enabling user enumeration

ID: security/timing-attack-user-enumeration-login

Also available as: JSON · Markdown · 中文
85%Fix Rate
82%Confidence
1Evidence
2023-08-05First Seen

Version Compatibility

VersionStatusIntroducedDeprecatedNotes
Node.js 20 active
Python 3.11 active
Java 17 active

Root Cause

The authentication logic performs a password hash comparison only after a database lookup confirms the user exists, causing a measurable time difference between requests for existing and non-existing usernames.

generic

中文

认证逻辑仅在数据库查询确认用户存在后才进行密码哈希比较,导致现有用户名和不存在用户名的请求之间出现可测量的时间差异。

Official Documentation

https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html

Workarounds

  1. 90% success Always perform a dummy password hash comparison, even when the user does not exist. In Node.js with bcrypt: const hash = user ? user.hash : DUMMY_HASH; await bcrypt.compare(password, hash);
    Always perform a dummy password hash comparison, even when the user does not exist. In Node.js with bcrypt: const hash = user ? user.hash : DUMMY_HASH; await bcrypt.compare(password, hash);
  2. 85% success Use a constant-time comparison function like crypto.timingSafeEqual for all password checks, and ensure the same code path executes regardless of user existence.
    Use a constant-time comparison function like crypto.timingSafeEqual for all password checks, and ensure the same code path executes regardless of user existence.
  3. 80% success Implement a generic error message for both invalid username and invalid password, and log the specific reason server-side only.
    Implement a generic error message for both invalid username and invalid password, and log the specific reason server-side only.

中文步骤

  1. 即使当用户不存在时,也始终执行虚拟密码哈希比较。在Node.js中使用bcrypt:const hash = user ? user.hash : DUMMY_HASH; await bcrypt.compare(password, hash);
  2. 对所有密码检查使用常数时间比较函数,如crypto.timingSafeEqual,并确保无论用户是否存在,都执行相同的代码路径。
  3. 对无效用户名和无效密码都实现通用错误消息,并仅在服务器端记录具体原因。

Dead Ends

Common approaches that don't work:

  1. 50% fail

    Adding artificial delays to all responses increases latency for legitimate users and may not fully mask the timing difference if the delay is predictable.

  2. 60% fail

    Removing the user existence check entirely breaks the authentication flow and may allow login with any password for non-existent users unless handled carefully.

  3. 40% fail

    Using a simple sleep() function after a failed login is easily bypassed by attackers who can sample multiple requests and average out the delay.