Password Security in ColdFusion Wrap up - Security Series # 4.6
I've got a few more tidbits on Password Security that I would like to discuss before I wrap this piece up and move on to something else. Some of this I may have covered before, but repetition is good, and some of it is new.
- Unless you have a specific reason to do otherwise, passwords should always be hashed. Two-way encryption may be acceptable if you have a legitimate reason to reverse the encryption and get the plain text password. But for most of us, hashing is the better choice since it cannot be reverse. And I should not have to say it, but I will, NEVER NEVER NEVER NEVER NEVER NEVER NEVER NEVER NEVER NEVER NEVER NEVER NEVER NEVER NEVER NEVER NEVER NEVER NEVER NEVER NEVER NEVER NEVER NEVER NEVER NEVER NEVER NEVER NEVER NEVER EVER store passwords in plan text. If anyone has a good reason to store a password in plain text, I would love to hear it.
- Where ever possible, protect your login pages with SSL. Even if you have a form post to a CFM page which hands the info off to a secure CFC and DB connection, the connection between the browser and the server is still open to sniffers that could intercept that data. SSL will keep that data safe from sniffing
-
Never pass username and password on the URL string.
Even in an SSL connection, the URL is not encrypted
UPDATE: It turns out that when you pass a GET request over SLL that the query string is, in fact, stripped off of the end of the request, added to the encrypted package and transmitted securely. And only the URL is sent "in the clear". It probably still is not a great idea to send the user/pass this way, so I am going to keep that in as a best practice. - Consider creating an audit log of login attempts, IP addresses, and user/pass combinations that are used. It can help you in a few places, including providing evidence to authorities of a hacking attempt
- Monitor your audit log with a scheduled task. Set a threshold that will send you an e-mail or SMS warning if the threshold is reached in a certain time period. For example, on a small site you might have it warn you if more than 100 login attempts were made within a one hour period
- If you lock a user after a certain number of login attempts, do it with a flag in the database, not with a session varaible. Sessions can be reset pretty easily by the end user by deleting their session cookie or by restarting their browser.
- Don't forget that anything you do for password security when users create their accounts or log in, you must also do when they change their passwords.
Proper password protection could someday save your bacon. It is one of those thankless task that we developers must do. If we do it excellently, no one will ever know or care, but if we mess it up, they'll keelhaul us.
My next sub-series will be on session security and session management. But I still have research to do, so it may take a week or so to begin the series. In the meantime, I will have other posts on security topics that are not part of a series.
Thanks for reading. Please tell me what you think of the series so far in the comments of this post. I really want your feedback and I would like to improve where improvement is needed.





http://www.usefulconcept.com/index.cfm/2008/1/4/Pr...
I find that if I put a picture of Mr. T below my login forms with a caption reading, "I pitty the fool who tries to brute force my login box"... then pretty much no one ever tries anything. Sure, I guess that's security through intimidation, rather than technology, but it works pretty good.
Actually I found a thought I had, but forgot, that may be pretty good.
"Take the session.uuid (created at start of session) and session.logginattempt (defalt 0). hash the uuid with the logginattempt as the salt. then store that as the hidden form field. Upon submit check that it exists and is correct. if fail, then increment session.logginattempt and use that increment as salt for the hash then use that new hash as the new hidden value, repeat. after session.logginattempt GT 5 lock em for the duration of the session.
I think that would force sessions, prevent bots. Some joker could manually type, but only a few times before being locked out. This way it doesn't lock real users from trying. And nothing to store / query from db other than what we already need for authentication."
If a bot can create a new session, theoretically, won't their login attempt never be greater than 1? ooooh wait, I see. If the hashed session ID does NOT match their proccessing page session ID (which is would NOT if the session was not maintained), then they get denied.
Cool idea.
The issue I see, and please correct me if I am wrong, is that the bot can delete its own session token cookie. And then the server has no way of knowing that the bot has ever been there before. Even if you went by IP or any header information, that can all be spoofed.
But essentially, every time the bot visits the page and attempts a login, it would delete its cookie (and maybe spoof a new IP) and visit again. To the server, the bot would be a new user on every visit and session.loginattempts would never be greater than 0.
Maybe I am not understanding the suggestion, but I think that any kind of control through session variables can be worked around by deleting that session cookie and hence, removing session persistence.
Err thats my theory anyway. :-)
I think what Josh is suggesting is that if:
1. You are currently processing the form
2. The session is JUST starting
.. then the session is not valid. Any user who filled out a login form and submitted it would be on the *second* page of their session when you process the form. However, if you are a bot and the session is *not* carried from the form page to the processing page, then the session will have just started on the processing page.
That's part of what I got out of it.
I am thinking along the lines of the bot actually visiting the login page and filling in data. then deleting the session cookie, revisiting the page, getting a new token and trying again.
Login page:
-------------------------------------
<form action="process.cfm" method="post">
<input type="hidden" name="id" value="#SESSION.CFID#" />
<input type="hidden" name="token" value="#SESSION.CFTOKEN#" />
...other fields.
</form>
-------------------------------------
The login page passes along the cfid/cftoken information. We would hash it or something, but I am keeping it plain for explanation.
This submits to the process.cfm page:
-------------------------------------
<!---
At this point, the user should already have a session carried over from the login page. Therefore, the current session cfid/cftoken should be the same as the one passed through.
--->
<cfif (
(SESSION.CFID NEQ FORM.ID) OR
(SESSION.CFTOKEN NEQ FORM.TOKEN)
)>
<!--- Wait, the session does not match. This means the current user is not holding their session from page to page - probably a BOT. --->
<cfabort />
</cfif>
<!--- ASSERT: If we have made it this far then the user has maintained their session and is less likely to be a bot. --->
-------------------------------------
Does that make any more sense?
What you are suggesting is a good idea, and would slow down the hackers, maybe enough to convince them to move onto easier prey, so it is definitely worth doing. I think it is a good idea, and I have actually used a similar methodology on a "Delete My Account" form to keep people from hitting the form action page (old skool) remotely and deleting users for fun.
UPDATE: It turns out that when you pass a GET request over SLL that the query string is, in fact, stripped off of the end of the request, added to the encrypted package and transmitted securely. And only the URL is sent "in the clear". It probably still is not a great idea to send the user/pass this way, so I am going to keep that in as a best practice.
I believe is should work for both scenarios.
SCENARIO ONE - via login form:
1. Bot requests page as is assigned session
2. Bot submit page (but does not submit cookies - LOSES session)
3. Processing page assigns bot a new session
4. Submission session info and current session info do not match --- BOT detected
SCENARIO TWO - form bypass
1. Bot submits form data to processing page directly
2. Processing page assigns bot new session
3. Submission session info (blank or expired most likely) and current session info do not match --- BOT detected
Of course, if the bot can submit cookies back to the server, then this becomes harder.
1. Bot requests page aand is assigned session
2. Bot submit page (AND DOES submit cookies - does not lose session)
3. Processing page processes login request because form token and session token match
4. Session.loginAttempts is upped by 1
5. Bot Deletes session cooke (resulting in lost session)
6. GOTO step 1 (Bot never detected)
I would create two columns in the users table called "suspended Integer(1)" and "suspendedDateTime date". Suspended would contain a 0,1, or 2. After three failed attempts for that user to log in, the account would be suspended for 30 minutes. All login attempts would check for that database flag, if the flag was "0" the account is not suspended. If the account was suspended with a "1" then the suspended data would be compared to Now(). If the difference was greater than 30 minutes, the request would be allowed, if not, it would be rejected with an appropriate warning. If the login attempted failed three more times during the suspension period, the suspended value would be changed to a "2" and the clock reset. Then it would be suspended for a longer period, an hour, a day, until support was contacted, whatever.
This would not prevent a well planned brute force attack that was set to try three passwords, wait 30 minutes, try three more, etc. But you could work in logic for permanent suspension after 9-12 attempts or something. The nice part is, the Database is controlling your account locking, not the session.
You could do something similar to block IPs with many failed login attempts. IPs can be spoofed, so it is not perfect, but it can slow down the hackers enough that many of them would just look for different site to exploit.
@Jason,
I had not thought of that scenario. I guess that makes my method not effective :( Good thinking.
I'm not saying that you can do it anywhere else that on the server. I'm only raising the point of the conflict between the need to protect the password of a user and the fact that this protection opens up an opportunity to disclose usernames - and I thought I heard you repeat that the username is half the authentication... the weakest half, I'll allow: if for nothing else, because password strength rules are never applied to it.
I'm not saying that we shouldn't adopt any account blocking policy, either: only that we need to be aware of the side effect. By blocking accounts, I know that I may give out usernames, but at least I'm making password disclosure a lot more difficult.
Maybe, however, can we adapt this policy to lower (slightly) the risk of username disclosure. For instance, we could allow for a relatively high number of consecutive failures the first time (if we enforce strict password strenght rules, this first threshold can be quite high), and then reduce it to a very small number on any attempts made after we have blocked the account a first time (because then the attacker would have had his confirmation of the username anyway, so our priority becomes to stop him from finding out the password, too).
After some time of legitimate access use (say, a month), we'd reset the threshold to the initial value (for the next attacker). In case of repeated peaks of failures on a same account we could inform the user and force him to change his password, to throw all the attacker's previous work away.
Mind you, most of this is purely academic. There is another place where the vast majority of applications will disclose usernames fairly easily: the registration form. More and more web applications are actually quite nice to the attacker, by providing a smooth ajax request that checks whether the username is already in use, before even submitting the form. All the attacker needs to do is to look for this request in his Firebug console and write a bot to launch zillions of requests to that script, and in no time he'll have a long list of valid usernames.
If we want to seriously protect usernames, we should never implement such a script and, when processing a registration form submission, only inform the user on a username conflict if all other checks are passed. At least that would force the attacker to create a bot that fills in the whole registration form with valid data - and actually create fake accounts.
Obviously I realise that this is not that much more difficult to do. A really robust solution would be to generate the usernames yourself and force any new user to use the one you have created for him. That would remove the need for any feedback on the uniqueness of the username. Otherwise, we may as well assume that our application's usernames aren't a secret for any serious attacker.
Sorry for the length of my comments! I try to shorten them, but the subject is huge.
Andrea
That said, I have 2 ideas for counter the hackers in the scenarios you mentioned. This are hypothetical, as I just thought of them, but they might be worth exploring further.
1. I was thinking, why would blocking an account at the username level have to reveal a legitimate account? Why not block any username (for 30 minutes) that has been used unsuccessfully 3 times. The 60 minutes after the next 3 unsuccessful. Then for 3 hours after the next 3, and then after that a permanent ban until admin reset.
So we would keep an extra table that stored the names on non-existent account that had been tried before. This would also help in timing our simulated attempts. Food for thought.
2. In a VERY secure application where we absolutely want to protect usernames from compromise, the "registration" form would not have a choice for username, the user would be given a username, hopefully in an unpredictable way, from the service. I realize this is an inconvenience the for the user, so i would only do it in an extreme circumstance.
As for the second, it is what I was suggesting at the end of my own comment. That's what you get when your comments are too verbose, I guess: readers doze off before they finish reading them... :o)
Anyway such a measure would indeed be very unpopular with the users, so I do agree that it is too extreme for most web applications.
Which makes the protection of the usernames a relatively low priority in our security policy because attackers will have an easy time collecting them via the registration form no matter how much effort we put in shielding them elsewhere.
Just as a side note, I think that a threshold of three attempts may be too low - unnecessarily so if you have strict password strength rules - for many people with even slight memory problems: it already would be for me now, and it would become probably worse in another 10 years time.