Joined: 17 Aug 2005
|Posted: Tue Jun 15, 2010 8:02 am Post subject: Security policies for Session, Logon and Password managment
I'm in the early stages of designing an ASP.NET based enterprise management system. I thought I'd shared the security policies we've tentatively settled on after a fair amount of research - partly for feedback, and partly as a point of reference for anyone else going through the same process.
The data is business critical, accessed by ~3000 users (100,000+ down the track), from PCs we do not control. We realistically aren't after military or banking level security, but we do need a reasonable level of security, balanced against ease of use.
Sorry for length, it's basically a copy/paste from our internal documentation.
• All communications over SSL, valid SSL certificate, forced by server
• Session key stored as cookie, in format: (*)
base64(binary(<domain:port>,<database_name>,<user_id>,base64(<crypto-strength random 256 bits)))
• Set cookie to "secure"
• Set cookie to "HttpOnly"
• Server forces HTTPS, with a valid SSL certificate
• Cookie to expire on browser shut down
• Cookie limited to exact domain & port
• Server checks session key against domain:port (*)
• Server checks session key against original IP address (*)
• No cross-site scripting (XSS) or user generated HTML/script in the entire application
• Server stores session key in database (sessions table)
• Server bubble-caches session object in ASP.NET Cache object (unreliable but fast)
• Sessions expire on explicit log off, or, after 2 hours of inactivity (*)
• We do NOT rotate session keys on a regular basis. This is complex to implement and adds little to no security for the following reasons:
Whatever mechanism gave the hacker the original session key can be used to get the new session key
However the server distributes the new session key could end up with the hacker instead of the victim
• Limiting session keys to a specific domain:port doesn't add much security, but seems like a good check, and is easy to implement
• Limiting session keys to a specific IP address is controversial and potentially problematic for load-balancing proxy users, but I think it will work in our situation. it's a good layer of security, and we can disable or enhance this down the track if it proves to be too much of a problem.
• Expiring after 2 hours of inactivity seems a long time, but is necessary for technical reasons for our bubble-caching mechanism, and isn't unreasonable.
• Server only accepts salted password hashes for authentication login
• Log all login attempts, results and IP addresses in log_on_attempts table
• For the given IP address:
If (IP address found in ip_addresses where black_list=1 and (black_list_expiry is null or black_list_expiry >= now)), then return "IP address blacklisted (until x)"
If (100 unsuccessful logins in past 24 hours) and (IP address not found in ip_addresses where white_list=1), then blacklist IP address for 24 hours. (*)
• For the given user name:
If (users.lock and users.lock_expiry_date is null or >= now), then return "user account locked (until x)"
If (10 unsuccessful logins in the past 24 hours), then lock the user account for 24 hours. (*)
• When login fails, return the reason. This is considered less secure than simply advising of logon failure, but is easier for the user to identify what they've done wrong, ie. one of the following:
IP address black listed (until x)
User account locked (until x)
Invalid user name
Disabled user account.
• If login unsuccessful (for any reason), pause (unsuccessful attempts in last 24 hours from this IP address) + (unsuccessful attempts in the last 24 hours for this user account) seconds before returning
• If login unsuccessful (for invalid user name), suggest "Resend your login details?"
• If login unsuccessful (for invalid password), suggest "Reset your password?"
• If login unsuccessful (for disabled user account), suggest contacting head office.
• Setting the maximum invalid logon attempts per IP address to 100 is high, but reduces the chances of blacklisting a legitimate IP address in cases of shared office or proxy. It's extremely unlikely that our medium security passwords will be cracked in < 100 attempts.
• Locking user accounts after 10 unsuccessful attempts allows a denial of service attack against a user. This is unavoidable, but unlikely, and the consequences are acceptable.
• General note - we do NOT use CAPTCHA technologies to verified users. This is hugely complicated to implement (accessibility issues, implementing audio equivalents, generating graphics automatically, conditionally verifying them in login attempts), and they're almost universally a pain in the backside for users (especially these days, when many CAPTCHA images are hard even for users to interpret, let along robots). The security CAPTCHA technologies add is unnecessary considering our user/ip address black listing policies and logon failure slow-down approach.
• Passwords in the database are hashed as exact and lower case (*), including lower(user_name) and a 32-bit salt value:
hex(sha256(binary(lower(user_name) + password) + 32_bit_salt))
hex(sha256(binary(lower(user_name) + lower(password)) + 32_bit_salt))
• New passwords must meet the following criteria:
Can't be cracked via on-the-fly dictionary attack, including reversed, and including suffixes "1-99"
Can't contain strings of our company name or system name
Contain at least 3 numbers and 3 letters
Text portions of password 3+ characters long must not be contained in the user name
(eg. user name "bob.smith" password "bob123")
Text portions of user name (split by " ", "." and "_") 3+ characters long must not be contained in password
(eg. user name "bob.smith" password "smithy272")
• Randomly generated passwords are:
8 characters long
Mix of letters and numbers
Exclude characters "1,0,o,j,i,l"
• Passwords can be reset via SMS, with the following process: (*)
Verify the user name
Verify it has a mobile number
Generate new password
TXT the new password to the mobile
If an email address is associated with the user, email a notification (but not the password)
• Storing the hash of lower(password) allows us to make passwords case insensitive. This is less secure, but reasonable in our environment for several reasons:
The vast majority of passwords are already simply in lower case, upper case or proper case anyway (rather than complex mixed case)
The individuals conscious of and desiring higher password strength and still increase strength using non-alphanumeric characters
Password strength is enforced through other measures (length, simulated dictionary attacks, mixing alpha/numeric, comparison to user name)
And the benefit: We don't have to deal with support calls for caps lock or forgotten case sensitivity
• Storing the exact hash of (password) as well as lower(password) allows us to support authentication for external applications which ARE case sensitive, down the track.
• General note - we don't have "secret question" style password resets. Password resets via TXT are simpler and reasonably secure.[/u][/b]