Cookie Security

463 views
Skip to first unread message

Kenny Daniel

unread,
Mar 12, 2013, 5:17:20 PM3/12/13
to secure...@googlegroups.com

I just wrote this long response on a closed pull request on github. Figured I would repost it here to generate more discussion.

-------------

The new SecureSocial authenticator cookies improve security, but are NOT stateless. This is an admitted downside of the new authentication scheme.

I'm the one who advocated for changing the cookies to the new system. Losing the statelessness isn't something which I really thought about at first. Now it is more clear to me that there is a tradeoff between security and statelessness when it comes to authentication.

1) If we use play's stateless authentication (a signed cookie with the user and expiration date embedded in the cookie in plaintext), then it is impossible to prevent cookie replay attacks, it is impossible to detect when a cookie is compromised, and it is impossible to expire cookies when the user logs out or changes their password. But it's fast, because it is stateless.

2) If we use the new authenticator cookie, then we need to do a Cache or DB lookup to verify every request. This is not really ideal, for the scaling and performance reasons mentioned by @adaptorel.

I think the best of both worlds would be the following: There should be a stateless, non-persistent, play-signed "session" cookie used for authenticating each individual request. This cookie should be similar to the old securesocial cookie, with a short expiration time (10 minutes? 60 minutes? whatever.) Then there should be a 2nd cookie which is the "login" cookie. The login cookie is like the new securesocial cookies. It uses a randomly generated UUID which needs to be checked against the Cache or the DB. When the user signs in, the login cookie gets set. If the user connects, and has a login cookie, but not a session cookie (or it expired), then we check the login cookie against the DB, and issue a new session cookie.

That way, most requests get authenticated statelessly using the session cookie, and so sites scale well horizontally. But we still get most of the benefits of using login cookies, namely that sessions can be expired by the user or by the server. The stateful operation (checking the login cookie) only happens once every X minutes when the session cookie expires. This seems like a good tradeoff in security/statelessness.

Thoughts?

Kenny Daniel

unread,
Mar 12, 2013, 7:52:15 PM3/12/13
to secure...@googlegroups.com
This would also allow people to control the security/stateless tradeoff in their own securesocial.conf settings. 

1) Set the expiration time on the session cookie to infinity and you have fully stateless, but less secure site.
2) Set the expiration time on the session cookie to 0, and then you get the current behavior of authenticating every request against a database.
3) Set the expiration time to something short but reasonable (I like 10 minutes, but it depends on the use case of your webapp), then the first request by a user requires hitting the DB, but requests after that will use the stateless session cookies (until the user idles for more than 10min).

This would also make it easy for SecureSocial to have a checkbox for "remember me on this computer?", which simply enables/disables the persistent login cookie.

Martin Grotzke

unread,
Mar 13, 2013, 4:32:36 AM3/13/13
to secure...@googlegroups.com
Sounds good to me!

Cheers,
Martin
> by @adaptorel <https://github.com/adaptorel>.
>
> I think the best of both worlds would be the following: There should
> be a stateless, non-persistent, play-signed "session" cookie used
> for authenticating each individual request. This cookie should be
> similar to the old securesocial cookie, with a short expiration time
> (10 minutes? 60 minutes? whatever.) Then there should be a 2nd
> cookie which is the "login" cookie. The login cookie is like the new
> securesocial cookies. It uses a randomly generated UUID which needs
> to be checked against the Cache or the DB. When the user signs in,
> the login cookie gets set. If the user connects, and has a login
> cookie, but not a session cookie (or it expired), then we check the
> login cookie against the DB, and issue a new session cookie.
>
> That way, most requests get authenticated statelessly using the
> session cookie, and so sites scale well horizontally. But we still
> get most of the benefits of using login cookies, namely that
> sessions can be expired by the user or by the server. The stateful
> operation (checking the login cookie) only happens once every X
> minutes when the session cookie expires. This seems like a good
> tradeoff in security/statelessness.
>
> Thoughts?
>
>

--
inoio gmbh - http://inoio.de
Schulterblatt 36, 20357 Hamburg
Amtsgericht Hamburg, HRB 123031
Geschäftsführer: Dennis Brakhane, Martin Grotzke, Ole Langbehn

signature.asc

t.det...@uq.edu.au

unread,
Mar 27, 2013, 2:26:49 AM3/27/13
to secure...@googlegroups.com
I've just discovered this "feature" in SecureSocial, after wondering why my login session disappeared after every reload in the development environment.

TLDR: I think the best way to resolve this problem would be to go back to storing the authenticator data in the session, and mitigate the individual issues around stateless sessions separately.

I'm rather disappointed that one of Play Framework's major draw-cards (statelessness) was removed so casually, without any easy way to get it back. (The Authenticator object is directly used throughout SecureSocial, and I can't just swap it out for a stateless implementation.)

There are quite a few issues you mention with stateless authenticators, but I think we can resolve them separately with a better overall result.

it is impossible to prevent cookie replay attacks
 
Cookie replay attacks are impossible to prevent on an open channel, period. You could use One-Time-Cookies, but they're not really applicable unless you can be sure you'll never have concurrent requests for the same session. If you reuse any cookie data, then you'll be vulnerable for some period of time. I think http://guides.rubyonrails.org/security.html#replay-attacks-for-cookiestore-sessions outlines that data you don't want the user resetting shouldn't go in a cookie session (and SecureSocial doesn't), so we're really talking about unauthorized access here.

Using a stateless authenticator, you'll have from capture until the Idle Timeout to reuse the the captured token to get access. After that, you have until the Absolute Timeout to do whatever you're planning to do.

Using a stateful authenticator as currently implemented, you actually have longer, because the authenticator ID doesn't rotate when touched but the last used time does. The Idle Timeout is constantly refreshed, and you have until the real user times out or logs out. This will only be a better situation if your users tend to log out rather than just close their browser window, and only then if authenticated sessions tend to be short.

it is impossible to detect when a cookie is compromised

By "compromised", I'm guessing you mean "somebody broke the AES encryption on the session and forged a new one" or else we're talking reuse here. Yes, that would be bad, but it's an inherent part of cookie sessions. If you don't like/trust cookie sessions, then the better way to fix that would be to implement an alternate handler in Play itself. Alternately, it could be handled by composing the action with a wrapper that stores a copy/digest of the session on the server before responding and validating later requests against that data.

As there's already composition going on, providing this optional security layer would be pretty straight-forward. It would also be a more generally-reusable and comprehensive solution for the Play community.
 
and it is impossible to expire cookies when the user logs out

Well, maybe. This is really a question of session validity, not content. You could handle this with an object implementing a session ID cache interface, something like:

{
  def begin(userId: UserId, sessionId: String): Unit
  def isActive(sessionId: String): Boolean
  def end(sessionId: String): Unit
  def endAll(userId: UserId): Unit
}

This could do much the same as DefaultAuthenticatorStore, but critically it doesn't require you to save the session ID on every request. The default implementation would perform much more efficiently, but other implementations could also make isActive() less safe to improve performance further. (eg. local cache if seen recently, else try shared cache) Looser guarantees also handle the problem of using an asynchronously-replicated data store for session ID validation (eg. Riak or CouchDB).

A no-op implementation would also provide excellent performance for applications where logout is less important. (eg. when only using OAuth authentication)

or changes their password

You can't tell if OAuth or OpenID providers have allowed a change in password, so this is really about the UsernamePasswordProvider. On changing your password, invalidating your session ID and reissuing a new one (as outlined above) it is perfectly sensible, so why not do that? For a password reset, it might make sense to call endAll instead, in case somebody has compromised the user's account.

I realise it might be a little more work, but I think it directly addresses the scalability issue and provides more flexibility to choose the right balance between security and performance. If I understand it correctly, I'm not really sure the session/login cookie solution provides the same level of security at the paranoid end of the scale.

How does that sound? (I'm open to implementing some of this if I've got a reasonable chance of having my pull-request merged.)

Tim Dettrick

Kenny Daniel

unread,
Mar 27, 2013, 4:24:59 AM3/27/13
to secure...@googlegroups.com
One thing worth clarifying... SecureSocial was never stateless to begin with. Even when it used play's signed cookies. It made a call to UserService.find() on every request in order to return an Identity object in "request.user".

To fix this I have been working on a STATELESS version of SecureSocial. Fork is available at https://github.com/platypii/securesocial

My solution uses 2 cookies:
 - 1 play signed cookie, which contains the user id, the provider, and a timestamp for idle timeout. (stateless)
 - 1 persistent authenticator cookie, which is used to authenticate the user if the session cookie expires. (stateful, but rare)
   - The autheniticator cookie can be revoked (logout, change password, etc)
   - Reuse of an authenticator cookie indicates suspicious behaviour

The other major change is in how the request gets annotated:
 - Added request.userId, which is equivalent to request.user.id, but it is stateless
 - Changed request.user to be a lazy val that retrieves the user Identity, it is stateful (bad!)
 - If you use this code you should avoid calling request.user.id, and instead call request.userId because it is "free"

I have been using this code in testing and it seems to be working nicely. I did some benchmarks, and it is FAST (since it doesn't need to do a DB call for each request)

I'm not quite ready to submit as a pull request yet. I would love to get more feedback! tim? jorge? :-)

 -- Kenny

Martin Grotzke

unread,
Mar 27, 2013, 4:51:05 AM3/27/13
to t.det...@uq.edu.au, secure...@googlegroups.com
On 03/27/2013 07:26 AM, t.det...@uq.edu.au wrote:
> I've just discovered this "feature" in SecureSocial, after wondering why
> my login session disappeared after every reload in the development
> environment.

Btw, to come around this for now you can use an ehcache.xml with
diskPersistent="true".

Cheers,
Martin


signature.asc

t.det...@uq.edu.au

unread,
Apr 22, 2013, 3:55:01 AM4/22/13
to secure...@googlegroups.com
Kenny,

Thanks for posting the link to your fork.

One reason for my concern around this was some odd behaviour on dev server reloads - not being able to log in without restarting the server entirely. Your fork could have been faster (I'm not sure) but it shared the same issues when using diskPersistent EhCache. That's not surprising though - you weren't trying to remove that cache dependence entirely, just slim it down a bit.

In the end, I made the switch to play-authenticate, which is as stateless/stateful for conversational state as you want depending on how you implement its UserService. It's nowhere near as convenient in many ways, but the extra flexibility was worth it once I wrapped my head around it.

Tim

Fraser Hardy

unread,
Aug 3, 2015, 10:01:13 AM8/3/15
to SecureSocial, t.det...@uq.edu.au
Hi All, 

I just wanted to re-open this discussion as we've had this exactly issue be flagged up by a penetration testing company who have said they were able to perform a replay attack even once the user had logged out. Because the session isnt closed on the server, while someone has a valid session cookie which hasn't expired they can use this to access the system again as that use. 

We're putting a solution into place now using a distributed cache to try and keep the statelessness of the application but I wanted to see if there was anything more developed on this as I cant find the fork mentioned in the previous post. 

Thanks, 
Fraser 

t.det...@uq.edu.au

unread,
Aug 9, 2015, 10:47:26 PM8/9/15
to SecureSocial
I think this Q&A on security.stackexchange.com sums things up perfectly:
http://security.stackexchange.com/questions/71753/stateless-session-cookie-and-logout

Stateless sessions imply a replay vulnerability. This is a limitation of the technique, not a design flaw. Encrypt your transport using TLS (ie. HTTPS) so the session cookie remains a secret shared only by the server and the user for the period that it's valid, and ensure that at no point do you put data in your session that cannot be safely replayed by the user.

If you cannot count on your user's client to keep the cookie secret, then you probably can't count on it to keep the initial authentication details (eg. username/password) secret either. How much you care about that rather depends on your application. If you require the ability to revoke sessions independently of the client, then you're going to need some server-side state and you'll need to balance how you implement that against performance and resource considerations.

I've long moved on from SecureSocial, so I've no idea how it does things now, but I remain a user of Play Framework. However you choose to manage your sessions, I would say the one essential thing is that you understand how it works so you can make an informed decision about the risks of whatever mechanism you choose.

Tim

Vít Šesták 'v6ak'

unread,
Aug 10, 2015, 2:09:49 PM8/10/15
to SecureSocial, t.det...@uq.edu.au
I can't agree with your justification about ignoring the replay vulnerability.

Imagine your credentials got compromised. But you change your password.

Without the replay vulnerability, there are two possible scenarios:
a) An attacker has changed your password in advance. Maybe you'll have to reset your password, maybe your account is permanently lost. Even if your account is lost, you don't add there new information, so the impact is somehow limited. Changing password is usually not a smart step.
b) An attacker has not changed your password. Old data might have been compromised, but nothing else.

It is, however, necessary to either limit the session lifetime or to remove all sessions on the password change. I prefer the latter approach, although it might be somehow harder to design properly when considering eventual consistency.

With the replay vulnerability, the attacker might get the session before you changed the password. After you change the password, the attacker will still have access to your account (and to associated data) without being noticed.

Some tradeoff might be justified, though. Imagine a stateless session valid for a limited amount of time that has to be periodically revalidated against some token saved in database. Also, some eventual consistencymight be implemented at the database. Such tradeoff might provide reasonable security together with reasonable fault tolerance.

But I would not implement that probably until it seems to be necessary in near future. Until the service grows enough, centralized database will be probably
good enough and there will probably be some places where investment is more justifiable.

Vít Šesták 'v6ak'
Reply all
Reply to author
Forward
0 new messages