Thoughts on Web Application Authentication
Anurag Agarwal posted an interesting article about
refining brute-force attacks∞ against web applications. Anurag suggests that instead of simply firing up a cracking tool against a particular application, it's useful to register repeatedly at the target site, iterating through the field of possible character combinations that could be used for a password, making note of what's required and what's forbidden. In doing so, an attacker can eliminate character classes that the application does not allow in passwords, and determine which patterns may be required to build a valid password. For example, the target site may not allow spaces in passwords, or might require that a password contains at least two numeric characters.
Essentially, the idea is that prior to brute-forcing a login, it makes sense that a cracker may ask the application itself what constitutes a valid password. Doing so reduces the pool of potential password candidates, and can make a brute-force attack more effective and less time consuming. Some applications don't require this step; they're more than happy to tell you their requirements up front and in plain language, with no testing necessary. It's not uncommon for a registration form to contain a hint along the lines of "password must be at least 6 characters and contain a capital letter." Applications such as this are inherently more susceptible to cracking by a determined attacker.
While authentication is a constant battle between security and user-friendliness, security should win out wherever possible. Although it seems obvious, this isn't always a viable route, and as developers, we're often faced with the conundrum: do we build our registration and login modules to be descriptive and verbose (easier to crack), or ambiguous and more secure (potentially confusing for users)? The answer is different for every situation, and there's no perfect balance. A website built for the masses will attract users who expect to be told how to construct a username and password that will work the first time they submit the form, and who will grow frustrated if they have to make multiple attempts before they're able to register successfully. One thing I like to bring up during discussions such as this is that it's quite possible to compromise; you can build an application that's as secure against brute-forcing as possible, while still appealing to your users.
Here are four guidelines for anyone developing an application with a login or authentication system.
A web application must not, under any circumstances, leak information about user accounts.
In order to brute-force a password, an attacker must first have a valid username on the system. The ability for someone to obtain a login is out of your control; from phishing to trojans, if you operate a high-profile website, your users will be compromised. Unfortunately, far too many web applications hand this information out for free by providing different responses depending upon the type of login failure. Consider a website that responds as follows to two login attempts:
username: bob
password: 1234
"Invalid username. Please try again."
username: john
password: 1234
"Invalid password. Please try again."
You may think that by providing a conditional response, you're merely assisting the user by hinting at precisely what he did wrong; however, you're also opening yourself up. In the above scenario, after two attempts, the attacker now knows a valid username, and can begin brute-forcing a password for this account. Error messages relating to authentication must always be consistent. Your application must not distinguish between situations where the username is outright unrecognized vs. the username being valid but with an incorrect password. There's nothing that prevents you from being descriptive at the same time: "The username and password you provided do not match our records" covers all cases and does not provide a potential attacker with any helpful information.
A generic web application should accept any standard ASCII password
This one is plain and simple.
Unless you're writing a financial application, you really shouldn't give a shit what your users choose as a password. If your application provides the ability for customers to make monetary transactions or transfer funds, then yes, you should set and enforce a standard for secure passwords (and you should also be using a combination of at least two of Something They Are, Something They Have, or Something They Know). However, the average forum / blog / community website should be open to accepting whatever passwords its users wish to register. Specifying a minimum password length is a decent compromise, but don't devise complicated rules that a valid password must be at least eight characters long and contain a capital letter and a number and a punctuation mark. Doing so is a disservice to your users, and helps to assist crackers as Anurag has postulated.
A blog or web forum is an unlikely target for brute-forcing, and thereby should happily accept "1234" or "password" as a user's password. After all, you're not storing your users' passwords in plaintext, are you? Which brings us to my next suggestion.
Passwords must never be stored in plaintext
When developing an authentication module, the primary rule should be that passwords are not stored as entered. I don't care whether you're writing your own blog or you're developing the web-based interface to e*Trade, this rule must not be broken. You don't store the password. EVER.
When this rule is enforced, any user's password cannot be retrieved in raw format by anyone, not even the administrators of the application, or someone who manages to locate an SQL-injection exploit. No one should ever be able to determine what a user's password is. There are several appropriate methods of storing and checking passwords without using plaintext. The most widely-used scheme is to save a hash of the password, and compare against it upon login attempts. That is to say, if a user registers with a password of "foobar" you would store, for example, the md5() of "foobar" in your database. Upon attempts by this user to login, you would md5() the password provided, and compare it to the stored md5()'d password. This stops your application from leaking actual passwords, even if an SQL vulnerability exists.
You may realize that such an approach prevents your application from being able to implement an "oops, I forgot my password, email it to me please!" feature. That's by design, because...
Passwords must never be sent via email, they must be reset instead
If a user has forgotten his or her password, an application must not provide the ability for the existing password to be obtained, or for a new password to be set, entirely via web interaction.
Forgotten passwords are a common occurrence, but from a security standpoint, they must be treated as hostile situations. A web application must not allow a user to set a new password without first sending email from, or confirming receipt of an email addressed to, the on-file address for the user in question. In the event that a user knows his login but has forgotten his password, a mechanism should be provided such that a new generated password is sent to the email address associated with the username. If a user has forgotten his login entirely, a similar process should be employed, whereby entering a recognized email address will result in an email message with the associated username and a new, freshly generated password.
Conclusion
Your web application is never going to be secure against brute-force attacks, especially if the attacker is aware of a username ahead of time. However, developers should be aware that certain precautions can help to harden web applications against cracking.
There are no comments on this page. [Add comment]