Charles Miller's article, "Persistent Login Cookie Best Practice,"[1] describes a relatively secure approach to implementing the familiar "Remember Me" option for web sites. In this article, I propose an improvement that retains all the benefits of that approach but also makes it possible to detect when a persistent login cookie has been stolen and used by an attacker.
Review
To summarize Miller's design:
When the user successfully logs in with Remember Me checked, a login cookie is issued in addition to the standard session management cookie.[2] The login cookie contains the user's username and a random number (the "token" from here on) from a suitably large space. The username and token are stored as a pair in a database table. When a non-logged-in user visits the site and presents a login cookie, the username and token are looked up in the database. If the pair is present, the user is considered authenticated. The used token is removed from the database. A new token is generated, stored in database with the username, and issued to the user via a new login cookie. If the pair is not present, the login cookie is ignored. Users that are only authenticated via this mechanism are not permitted to access certain protected information or functions such as changing a password, viewing personally identifying information, or spending money. To perform those operations, the user must first successfully submit a normal username/password login form. Since this approach allows the user to have multiple remembered logins from different browsers or computers, a mechanism is provided for the user to erase all remembered logins in a single operation. The Problem
Miller correctly describes the many advantages of this approach. One disadvantage, however, is that if an attacker successfully steals a victim's login cookie and uses it before the victim next accesses the site, the cookie will work and the site will issue a new valid login cookie to the attacker (this disadvantage is far from unique to Miller's design). The attacker will be able to continue accessing the site as the victim until the remembered login session expires. When the victim next accesses the site his remembered login will not work (because each token can only be used one time) but he's much more likely to think that "something broke" and just log in again than to realize that his credentials were stolen. Displaying a "last login time" may help the user notice the problem but, frequently, it will go undetected.
One possible solution to this problem is to treat the presentation of an invalid login cookie as evidence of a previously successful attack. The site could then present an impossible to miss security warning and automatically invalidate all of the user's remembered login sessions. This approach would create a denial of service attack: since usernames are easy to come by or guess, an attacker could submit invalid login cookies for every user and thus disable the entire system.
The Solution
My solution to this problem is based on the observation that since each token can only be used once, a remembered login session actually consists of a series of such tokens. When an attacker successfully steals and uses T_0 he is issued T_1, but the victim is still holding T_0. The database no longer has a copy of T_0 and thus cannot differentiate it from an arbitrary invalid token.
However, if the series of tokens is itself given an identity that must be presented along with the current token, the system can notice that the victim is presenting a valid series identifier along with an invalid token. Assuming that the series identifiers are as hard to guess as tokens, the only way a user could present a valid series identifier with an invalid token is if some other user previously presented the same valid series identifier with a valid token. This requires that two different users held the same series and token pair at the same time and therefore indicates that a theft has occurred.
The implementation is no more difficult and requires no more resources than Miller's design. From the summary above, only items 2 and 3 change:
When the user successfully logs in with Remember Me checked, a login cookie is issued in addition to the standard session management cookie.[2] The login cookie contains the user's username, a series identifier, and a token. The series and token are unguessable random numbers from a suitably large space. All three are stored together in a database table. When a non-logged-in user visits the site and presents a login cookie, the username, series, and token are looked up in the database. If the triplet is present, the user is considered authenticated. The used token is removed from the database. A new token is generated, stored in database with the username and the same series identifier, and a new login cookie containing all three is issued to the user. If the username and series are present but the token does not match, a theft is assumed. The user receives a strongly worded warning and all of the user's remembered sessions are deleted. If the username and series are not present, the login cookie is ignored. It is critical that the series identifier be reused for each token in a series. If the series identifier were instead simply another one time use random number, the system could not differentiate between a series/token pair that had been stolen and one that, for example, had simply expired and been erased from the database.
Conclusion
This system has all the advantages of Miller's original approach. Additionally:
An attacker is only able to use a stolen cookie until the victim next accesses the web site instead of for the full lifetime of the remembered session. When the victim next accesses the web site, he will be informed that the theft occurred. This system is used by the Persistent Login module for the Drupal content management system.
Notes
[1] Miller, Charles. Persistent Login Cookie Best Practice, http://fishbowl.pastiche.org/2004/01/19/persistent_login_cookie_best_practice. January 2004. [2] For the most secure result, the standard session management cookie should be a "session" cookie that expires as soon as the Web browser is closed. Furthermore, the server should enforce a fairly short maximum lifetime on sessions even if the browser remains open.