David Heinemeier H. wrote:
Regarding authentication, I’ll strongly encourage people to try
rolling their own. It’s probably simpler than trying to compare the
prepackaged options out there. And you’ll learn a lot about Rails and
filters in the process.
Implementing basic session-based authentication is a breeze (and it’s
covered nicely in the Agile Web D. book). Here’s a few things
to have in mind when creating secure logins and cookie-based
authentication, though. Bear in mind, I’ve only been on rails for a
week, and I’m making assumptions here.
Storing user data in sessions
You’ll probably want to store the hashed password along with the user ID
in the session data. That way, a session will be rendered invalid if the
password is changed.
Session fixation / session stealing
The ID of an existing, authenticated session is all an attacker would
need to access protected data. There’s a few ways this could happen:
-
The attacker establishes a session with the site (ie, points the
browser to the site), and reads out the session id from the headers or
the browser’s cookies. He/she then cons an unsuspecting user to login
using his session. (in php, this is as simple as a link like
http://site.com/?PHPSESSID= - i’m not sure if rails even
supports supplying session ids via url parameters). This is called
session fixation.
-
Cross-site scripting. There’s a number of ways a malicious user could
make a javascript execute in your browser, read up on XSS for the
details. The result? The attacker is able to read data from your
cookies, including the session ID. Voila, instant backstage pass.
-
Sniffing on shared networks. Use HTTPS if you’re worried about network
sniffing.
The solution to blocking session fixation is dead simple: change the
session id or start a new session on login.
Making sure the session id is coming from the correct client is a bit
hairier. You could store the ip-adress in the session and verify that
aswell. That’ll make it more secure in many cases, but bear in mind:
some ISPs use proxy servers sharing a pool of ip-addresses, meaning the
requests could come from different ip-addresses. Also, the attacker
could be sitting on a local network sharing the same external
ip-address.
It’s probably safe to assume that there’s something fishy going on if
the User-Agent header string changes from request to request.
Change the session ID on every request if you’re absolutely paranoid,
though that will break concurrent requests.
And remember to clear out stale sessions as often as possible.
Storing logins in cookies
Storing the username and password in a cookie is asking for trouble. If
your site is vulnerable to cross-site scripting, not only would the
attacker be able to retrieve the session id - the clear text password
would be accessible as well. Plus, the clear text password would be
transmitted over the air on each and every request along with the HTTP
headers. Anyone with physical access to the machine would be able to
read the clear text password from the cookies. Don’t store sensitive
data in long-term cookies on shared terminals whatsoever.
In my PHP setup, I use a system with two tables:
users
username
hashed_password
users_login
user_id
remote_address
token
hashed_password
last_seen_at
On authentication check, I first try to find the username and hashed
password in the session. If they’re not present, I check if the username
and a token are provided via cookies. If the token is provided, I try to
get the hashed password from the user_login table with username, ip
address and token as keys. I then check the hashed password against the
real hashed password in the users table. If they match up, the user is
authenticated.
When a user successfully logs in (via the login form), I do two things:
- Store the username and hashed password in the session
- Unless the user already has a token set in the cookies, I generate a
new one. The token is a random hash. I set token and username in the
cookies, and create a new entry in the users_login table.
This solution assumes the user have a static IP-address. If the IP
changes, the user will have to reauthenticate on the next session. Not
much of an inconvenience for my users, but most are on broadband access
with more or less static addresses.
If I change my password, sessions and tokens on other machines would no
longer be valid. IMHO, this is desirable. (I might forget about logging
out of a public access computer, and change my password to ensure that
the old session no longer would be valid)
You could up the paranoia on this solution too… For example, if
authentication fails for a user (wrong password, wrong IP), you could
wipe that users tokens from the users_login table to reduce the chance
of a successfull hacking attempt, at the expense of having the user
relogin on the next session.
There’s no such thing as bulletproof security. The trick is to add as
much complication as you can to the authentication process without
inconveniencing the users too much. It all depends on how sensitive the
data in your application is, and how much the attacker would gain on
getting access (ie, how much effort he/she is willing to put in). Also,
remember: Less Software. An authentication scheme is as strong as it’s
weakest link, ie. the ugliest bug.
Inge Jørgensen
web developer/designer
[email protected]
[email protected]