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.

  1. 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.
  2. 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
  3. 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.
  4. 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
  5. 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
  6. 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.
  7. 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.

Related Blog Entries

Comments
Ben Nadel's Gravatar Your series is excellent. It definitely makes me feel guilty :) But in a good way - in the "I have lots of room for improvement" way. Thanks a lot.
# Posted By Ben Nadel | 6/4/08 6:31 AM
joshua cyr's Gravatar I think you could do a whole post about brute force login attempts and how to prevent. While I think the easiest way is to lock users, frankly that just lets anyone easily do a DOS attack on users so they can't log in. Which I hate. I blogged on the issue and ideas that could be used, but frankly there wasn't a great solution that didn't involve just locking the user after x attempts. Except maybe my comment on redirecting to login if no session exists. So that even if bot clears session it still can't submit. Combine that with a much more complicated DB based locking system and maybe...

http://www.usefulconcept.com/index.cfm/2008/1/4/Pr...
# Posted By joshua cyr | 6/4/08 7:43 AM
Ben Nadel's Gravatar @Josh,

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.
# Posted By Ben Nadel | 6/4/08 7:50 AM
joshua cyr's Gravatar @ben hahaha, well I know you intimidate me. :-)

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."
# Posted By joshua cyr | 6/4/08 7:52 AM
Ben Nadel's Gravatar @Josh,

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.
# Posted By Ben Nadel | 6/4/08 7:58 AM
Jason Dean's Gravatar @Josh and Ben -

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.
# Posted By Jason Dean | 6/4/08 8:26 AM
joshua cyr's Gravatar Think of it like captcha though. Even though session.loginattempts is zero, the hashed UUID is wrong, so there would be no login attempt. If the bot resets session the UUId from the Form wouldn't match. If the bot skips form, then the session UUID would still be different from what the bot sends as the hashed form UUID. Since the UUID is salted with the login attempt before hashed it forces it to properly authenticate each attempt if they do keep session.

Err thats my theory anyway. :-)
# Posted By joshua cyr | 6/4/08 9:38 AM
Ben Nadel's Gravatar @Jason,

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.
# Posted By Ben Nadel | 6/4/08 9:39 AM
Jason Dean's Gravatar Again, I don't see how you can prevent the bot from delete the session cookie and visiting the login page as a new user. The bot would then get a new session token from your server and your login form would need to reset the hashed hidden field because the server would think that it was a new user.

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.
# Posted By Jason Dean | 6/4/08 10:31 AM
joshua cyr's Gravatar Ah, I see. If the bot just never does a session, it would be working fine. But if the bot resets session only at form page, then submits that page... then the UUID would be valid and the lastlogin would never increment. blast!
# Posted By joshua cyr | 6/4/08 10:40 AM
Ben Nadel's Gravatar Guys, don't worry about login page, itself. That has no value to use since *everyone* can appear to have a valid session on that page. The login page then submits to "processing" page. This is where we make our move.

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?
# Posted By Ben Nadel | 6/4/08 10:49 AM
Jason Dean's Gravatar @Ben - That would show that the login attempt was coming from the login form and that the form was not being by-passed. So yes, that would stop those that are trying to write a bot that by-passes the form and posts directly to the processing page/function. But what if the bot is actually filling in the form and hitting submit. I'm thinking and attack written using Selenium or something like it.

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.
# Posted By Jason Dean | 6/4/08 11:10 AM
Jason Dean's Gravatar I just wanted to post a quick update I made to the blog entry.

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.
# Posted By Jason Dean | 6/4/08 11:11 AM
Ben Nadel's Gravatar @Jason,

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.
# Posted By Ben Nadel | 6/4/08 11:20 AM
Jason Dean's Gravatar @Ben - I was thinking of scenario three
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)
# Posted By Jason Dean | 6/4/08 11:31 AM
joshua cyr's Gravatar @Jason agreed. I hadn't considered scenario 3. I am sure that can be countered some other way, but want to take some more time thinking about it.
# Posted By joshua cyr | 6/4/08 12:47 PM
Jason Dean's Gravatar @Josh - The only way I can think to counter scenario three is at a per user level and I would do it at the database.

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.
# Posted By Jason Dean | 6/4/08 1:06 PM
Ben Nadel's Gravatar Hey guys, sorry to drop out of the conversation yesterday - I had to run to a friend's commencement ceremony at Hunter College.

@Jason,

I had not thought of that scenario. I guess that makes my method not effective :( Good thinking.
# Posted By Ben Nadel | 6/5/08 9:42 AM
Andrea's Gravatar The problem with temporarily blocking the user after x attempts is that while it protectshis password against a brute force attack, it will eventually give away a new username each time the attacker realizes a given account is blocked: the application can't just go on pretending the username or password is wrong. Should the real user try to access the application, he needs to know the real reason why his access is denied.
# Posted By Andrea | 8/23/08 12:21 PM
Jason Dean's Gravatar @Andrea, unfortunately, that problem is worth the trade off. There is no perfect solution to this issue. Since everything from the hacker can be spoofed, the only reliable means of control is via the server side components. Additionally, with the other controls in place, (Proper server logs, auditing, warning system, hashed and salted password, periodic password changes, delays preventing brute force attacks, password strength checks, etc.) if the hacker finds the occasional legitimate username, it will not do him much good, because he will be discovered long before he can brute force against the password.
# Posted By Jason Dean | 8/23/08 5:14 PM
Andrea's Gravatar Jason,

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
# Posted By Andrea | 8/24/08 11:31 AM
Jason Dean's Gravatar Andrea, I see what you are saying, and it makes good sense. The more I learn about the way hackers work , the more I think that spending too much time protecting the usernames is just wasted time. Most of the simply methods will atleast protect against the novice hackers and script kiddies, but as you said, if a real hacker is determined to find a legit username, they will. Which only enhances the need for the other password protection methods we have already discussed.

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.
# Posted By Jason Dean | 8/24/08 9:43 PM
Andrea's Gravatar Jason, your first idea seems quite effective. Probably we would also schedule some process to clean up usernames that haven't been probed for some time.

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.
# Posted By Andrea | 8/25/08 5:40 AM
Jason Dean's Gravatar @Andrea, agreed. 3 is a small threshold. Perhaps 5-10 would be more appropriate.
# Posted By Jason Dean | 8/25/08 10:51 AM
BlogCFC was created by Raymond Camden. This blog is running version 5.9.1. Contact Blog Owner