I am using graphql and my login function is resolved using a promise. The username is an email address.
The steps in the logic are the following: -
- Validate CRSF token else return generic response ("Invalid username or password").
- Verify the email's format is valid else return generic response.
- Verify the password's format is valid else return generic response.
- Select user record from DB where email = incoming email.
- If no record found, return generic response.
- If a record is found, compare hash of incoming password vs password hash in the found user record. If matched, login else return generic message.
The assumption is that the hashing of the password is the most time-consuming part. There are 2 approaches I am considering: -
OWASP gives an example of how to mitigate time-based attempts at step 4 above, which is to generate a random string and hash it regardless to disguise that the user record was not found.
As my login function is a promise, another option I have is to use an intentional standard delay on the resolving of the promise (easy to implement). The delay would be greater than the expected resolution time of the login function, which should serve the purpose of giving all login calls (for all intents and purposes) the same response time.
Does anyone have any opinions on which of these 2 methods would be best for this scenario? Thanks.
-
1As usual, if you can delegate authentication to someone else (an IDP, via oAuth2, OIDC, SAML...) then you should probably prefer this.jcaron– jcaron2024年10月29日 15:41:01 +00:00Commented Oct 29, 2024 at 15:41
-
@jcaron: I think your answer is the best one. It is never a good idea to roll your own authentication from abolutely scratch. If you do not want to go with authentication as a service providers (which is perfectly fine), it is a better choice to stick with a security framework (e.g. Spring Boot Security in Java). I am sure on npm there are "Spring Security" alternatives for NodeJs. Those frameworks already have measures against timing attacks, and many more things (correct password encoding, access/refresh token management etc.)oleg_zh– oleg_zh2024年10月29日 15:51:50 +00:00Commented Oct 29, 2024 at 15:51
2 Answers 2
How about not using the e-mail address as the username? If users can choose an arbitrary name (like on this site), then the login feature won't have to deal with any e-mail addresses in the first place and therefore cannot leak them.
Preventing timing attacks in a complex system like a web application which interacts with a database is difficult and may even be entirely impractical. On the surface, the authentication procedure looks like a short sequence of simple steps. But in reality, it involves plenty of low-level operations, caches and database indexes which all affect the overall timing. Even if you mask obvious time differences (e.g., by hashing a dummy password in case the username is invalid), chances are the system still leaks side-channel information.
The idea of setting a fixed response time also doesn't really work, because there's a fundamental difference between a system which is actually busy calculating a hash and one that just idles until some timer runs out. An attacker could, for example, purposely use up system resources (e.g., with lots of HTTP requests) and then trigger the authentication procedure. If the system is already under load and additionally has to perform an expensive hash calculation, this should lead to measurable response delays on other pages. If the login procedure just waits, this doesn't create any extra load and therefore no delays.
Even if you come up with more and more sophisticated approaches to mask timing differences, it's ultimately a cat-and-mouse game rather than an actual solution. If you don't want to leak e-mail addresses, don't use them during authentication (and generally as little as possible).
-
Not using an email address as the username is the best solution, but is it what the OP wants? It's possible not, but don't you think the OP would have considered this already if not using an email address as the username was an option? I don't know for sure, but the most likely reason for this would because the OP doesn't want users having arbitrary names. Which I understand, because on one of my applications I don't. Instead I do give users a choice as to whether they want a username that is derived from their email but doesn't actually expose it (e.g. first-name_random-numbers.)security_paranoid– security_paranoid2024年10月29日 02:39:31 +00:00Commented Oct 29, 2024 at 2:39
-
Thank you for your response. I will be implementing both DDoS prevention and auto-scaling. Surely this will counter an attacker's attempt at using up server resources?U4EA– U4EA2024年10月29日 10:08:41 +00:00Commented Oct 29, 2024 at 10:08
-
@U4EA: I don't mean exhausting resources like in a DoS attack. My point is that an attacker can distinguish between an actual hash calculation (which is expensive by design) and a fake delay by putting some load on the server, triggering a login and then measuring the response time on other pages. If the server is starting to get busy, this should lead to slightly slower responses (or any other measurable reaction, e.g., from the auto-scaling). If the delay is just fake, the authentication should have no impact on the response time, because the server doesn‘t actually do anything.Ja1024– Ja10242024年10月29日 11:21:32 +00:00Commented Oct 29, 2024 at 11:21
-
So, if email addresses are to be used as the username, do you think there is any point in trying to prevent timing attacks?U4EA– U4EA2024年10月29日 11:30:42 +00:00Commented Oct 29, 2024 at 11:30
-
@U4EA: No. Unless there's a strong argument for why you should implement this despite its major limitations (e.g., a superior thinks it's a great idea), I wouldn't do it.Ja1024– Ja10242024年10月29日 12:16:09 +00:00Commented Oct 29, 2024 at 12:16
To harden against timing attacks, just perform all of your login procedure steps regardless of whether their inputs are valid or the previous steps already determined an invalid login. Only at the end you'll either accept or deny the login attempt. So even if the account name is invalid, do a database lookup. Even if the password is malformed, hash it and compare against the stored hashed password (maybe using a constant salt if the database lookup did not return a user record).
-
You're just repeating what the OP already proposed. The question was: Should they create a dummy account to handle nonexistent usernames, or should they always send a response after a specific time? And I'll add a third option: Maybe they should do neither, because completely eliminating time differences is not as easy as it may seem (due to caches, indexes, slightly different execution paths etc.).Ja1024– Ja10242024年10月29日 14:02:35 +00:00Commented Oct 29, 2024 at 14:02
-
What I've written isn't exactly what OP proposed (it matches the first approach they mention, but differs in the handling of username and password format). Completely eliminating any time differences is indeed hard, but if the differences in execution paths are marginal (i.e. comparing against a database field vs. against a constant string) they get drowned in the noise already present by changes in server load, caching, etc. The main goal is to make timing analysis useless, but it is also desirable to disclose as little info about legality of user name and password as possible.Hans-Martin Mosner– Hans-Martin Mosner2024年10月29日 14:16:44 +00:00Commented Oct 29, 2024 at 14:16
You must log in to answer this question.
Explore related questions
See similar questions with these tags.