I have been looking at ways to guard against session-hijacking, where someone steals a session cookie and uses it to gain access to the system.
Programs such as http://codebutler.com/firesheep make it easy to sniff sessions on open Wireless networks, and other ways of getting sessions include cross-site scripting attacks, or just physically lifting them from a victim's computer.
Using SSL to secure all session-cookie/server communications is critical for preventing the Firesheep sniff, and setting HTTPOnly on the cookie helps prevent JavaScript from being able to read the session cookie in XSS attacks, but it's still vulnerable to AJAX-based attacks.
Another layer is to include a security token or a nonce in the session cookie that gets updated on each request. You store the token in a server-side datastore and in the cookie, and on each request you compare that the token in the cookie matches the token in the datastore.
If the tokens don't match that could be an indicator that someone stole the session and is trying to use it so you can either ignore the request or invalidate the session and require the user to re-authenticate. However, mismatched tokens could also result from a slow/flaky connection.
For example, you could have a case where the server receives a request from a real user, updates the session token in the server datastore and responds to the user with a session cookie that contains the updated token. But the user doesn't receive the response due to a slow/flaky connection so the user still has the old session token while the new one is stored on the server. When the user retries the request, the tokens won't match.
One way to mitigate this problem is for the sever to keep a history of the last few tokens and check that to see if they match, but then it becomes a situation of how many tokens to keep, and depending on how flaky the connection is or how click-happy the user is, the server may cycle through the history before the connection comes back and the user's session gets updated by the browser.
An alternative to keeping a token history is to timestamp each session and check if the timestamps are within some short, specified range, say 30 seconds. If the user's session cookie timestamp is within 30 seconds of the server's stored session timestamp, then the session is deemed authentic.
Example pseudocode
def authenticate_request():
if (stored_session.timestamp - session.timestamp > 30 seconds):
return False
return True
This avoids having to keep a token history -- the timestamp becomes the token -- but attackers have a 30 second window of opportunity to hijack the session after it's stolen. While this is true, the token-history alternative isn't any better because it gives attackers a potentially longer window of opportunity.
Other approaches of checking for IP address and User-Agent changes have issues too. User Agents are easily spoofed, and if an attacker is able to get a user's session, they can easily determine the User Agent through the same XSS code or some other means.
If the user is on a mobile device, their IP address may change frequently so that would result in many false positives. Furthermore, the attacker could be behind the same company firewall so the user and attacker's IP are the same to the external Web server.
Is using a timestamp token the right approach or is there a better way? Is the 30-second buffer about right? What edge cases am I missing?