Replay attacks with cookie session

80k cannot go in cookies. On most platforms the limit is 4k. The CookieStore raises an error if you try to store too much.

And it's a difference between grabbing the data from the DB to deserialize it and grabbing it from a cookie to deserialize it. The main driver isn't performance -- it's not having to keep all of those sessions around on the web server.

> Avoiding a single database lookup isn't the purpose of the cookie > store.

Sure, but you do remove a lot of the glamour of the cookie store if it has to be tied to a database for a nonce lookup anyway.

Smudge her mascara a little; she's still sexy in the morning.

I already User.find(session[:user_id]) per request, so perhaps my expectations are different from yours.

> This discussion skipped plugging the session replay hole. I understand > your concern, but I think you underestimate the average Rails > developer.

I might be over-concerned here, but I'm not talking about the average Rails developer. I'm talking about the worst ones. I can tell you that while the average Rails developer I've worked with would certainly be able to understand this and work around it, I'm not sure that the worst ones would even understand what the issue was if it were explained to them.

I don't think you're over-concerned. However, attempts to find a solution are not argument that session replay isn't a problem.

> For example: to prevent user_id replay, store a last access timestamp > in session that's updated on login and logout.

If I understand you correctly, you would also keep a copy of the latest timestamp server-side. I would submit that for many developers, storing a timestamp for each session on the server is not much more attractive than just storing the whole session on the server.

Let's talk about practical fixes rather than whether we should write it off entirely.

An application-level solution to user_id replay:

  Per request:     @current_user = User.find_by_id_and_last_seen_at(session[:user_id], session[:last_seen_at])

  On login:     @current_user.update_attribute :last_seen_at, Time.now     session[:user_id] = @current_user.id     session[:last_seen_at] = @current_user.last_seen_at

  On logout:     @current_user.update_attribute :last_seen_at, Time.now     reset_session

This requires one additional database query on logout to invalidate future reuse of that session and no additional per-request queries.

Anyone care to try a generic, automatic nonce and benchmark the results?

jeremy

This assumes you are doing a query per request anyway, and basically piggy backing on it for the security. If your application is already doing a query for the current user for every request, sure, there's almost no additional cost. However, if your application isn't doing a query per request, you have to add one to get the security, which makes the cost the same as using the database for session storage.

IMO, this is not a problem that the application developer should need to deal with manually, any more than application developers should handle database transactions manually. If cookie based session storage is going to remain the default:

1) There should be a configurable timeout set automatically, with a reasonable default (to partially mitigate the replay issue) 2) It should be documented that replay attacks are still possible 3) Advice on creating application specific solutions such as the one you mentioned should be included in the documentation

Note that timeouts provide only slight additional security, as they only protect against attackers who get access to a session cookie after it has already expired. If an attacker gets access to any session cookie before it expires, they can keep that session open indefinitely.

Jeremy

Good points. I worked up a quick plugin that uses DRb to store the nonce, and I’m doing some benchmarks on it now. I don’t have time for anything fancy, but we can at least get in the ballpark.

Or, to put it in Zed’s terms, we’ll just run it 100 times each way and compare the numbers. :slight_smile:

OK, here it is: svn://svn.madriska.com/plugins/raisin_cookies

To use, install the plugin and start up the DRb server from the DRb session store (action_controller/session/drb_server.rb).

The DRb server keeps a hash mapping session HMACs to the latest nonce used with the session. The nonce is verified along with the HMAC on unmarshal. That way, old sessions are invalidated because the HMAC of the session must be presented with the latest nonce stored in the DRb backend.

Right now, the code issues a new nonce on each update; it should probably check first to see if the data has been updated, but it doesn’t right now.

Very simple benchmarking shows a 10-15% impact on reqs/sec with this plugin. I would guess that most of that comes from the nonce generation (I use Rails’s CGI::Session.generate_unique_id, which has to create an MD5 hash for each session). I don’t really have the time to look further, however. I only tested with a single Mongrel. Does anyone care to do some performance studies on this?

Brad

Good points. I worked up a quick plugin that uses DRb to store the nonce, and I’m doing some benchmarks on it now. I don’t have time for anything fancy, but we can at least get in the ballpark.

> Let's talk about practical fixes rather than whether we should write > it off entirely.

...

> This requires one additional database query on logout to invalidate > future reuse of that session and no additional per-request queries.

This assumes you are doing a query per request anyway, and basically piggy backing on it for the security. If your application is already doing a query for the current user for every request, sure, there's almost no additional cost. However, if your application isn't doing a query per request, you have to add one to get the security, which makes the cost the same as using the database for session storage.

That assumption is stated at the beginning of the example:

> An application-level solution to user_id replay:

You reason that touching the database "makes the cost the same as using the database for session storage" which is likely wrong and certainly unsubstantiated.

Criticism is great and welcome, everyone, but let's add some creativity and code to the mix, please.

IMO, this is not a problem that the application developer should need to deal with manually, any more than application developers should handle database transactions manually. If cookie based session storage is going to remain the default:

We all agree on this. We're working on it.

1) There should be a configurable timeout set automatically, with a reasonable default (to partially mitigate the replay issue) 2) It should be documented that replay attacks are still possible 3) Advice on creating application specific solutions such as the one you mentioned should be included in the documentation

Good ideas.

Note that timeouts provide only slight additional security, as they only protect against attackers who get access to a session cookie after it has already expired. If an attacker gets access to any session cookie before it expires, they can keep that session open indefinitely.

Not in the scenario above: the legitimate user logging out invalidates the attacker's session, which is the same behavior as a server-side session.

jeremy

80k cannot go in cookies. On most platforms the limit is 4k. The CookieStore raises an error if you try to store too much.

"at least 4096 bytes per cookie ... at least 20 cookies per unique host or domain name"

Is there something in today's cookie implementation that prevents someone from doing 20 cookies at 4k each?

80k cannot go in cookies. On most platforms the limit is 4k. The CookieStore raises an error if you try to store too much.

RFC 2109 - HTTP State Management Mechanism

"at least 4096 bytes per cookie ... at least 20 cookies per unique host or domain name"

Is there something in today's cookie implementation that prevents someone from doing 20 cookies at 4k each?

I defer to my original statement:

The CookieStore raises an error if you try to store too much.

There is a hard-coded 4k limitation in the store, and the store will only use one cookie per session.

If you'd like to coordinate this, I'd definitely be glad to hear from them....I'm not sure that there's a shared nothing way to take care of that, but the crypto experts would know for sure.

Please do investigate having someone conduct a review.

Will do - I'll speak to some of my contacts and see if they're willing to do a short review, pro bono.

I'm not sure that there's a shared nothing way to take care of that

Exactly. Basically, you can mathematically prove that the only way to avoid replay attacks is with some type of trusted store. Which of course needs to be shared.

@Brad - Great work on setting up some code for benchmarking. I haven't reviewed your code, but doing nonces correctly gets into some sticky synchronization and concurrency. Basically, the system needs to make sure that:

* The nonces used by all of the Mongrels are kept track of (eg one counter per Mongrel)

* No Mongrel uses the same nonce (eg prefix the nonce with the Mongrel's pid)

* That the issuing a nonce, restoring a session, and then invalidating the nonce are all done in the correct order. This can get very tricky:

Specifically, we need to make sure that once the session has been modified, its nonce is invalidated immediately. At the same time, we can't invalidate it before a new session has been assigned, or those requests will be incorrectly rejected. This gets very, very tricky when you're dealing with multiple overlapping requests: invalidate too soon, and you've broken pipelining. Invalidate too late, and you're subject to replay attacks. Like I said, crypto is hard: amateur cryptosystems are usually broken with relish.

@Jeremy - you're raising a good issue. I think it would be beneficial to define exactly what the goals of cookie sessions are. Performance? Less administrative setup? Simplicity (I think that's been lost already)? Not having to store dormant sessions? I'll add that, IMO, an attraction to coolness and clever code is *not* a goal worth pursuing.

To get started, here's a good link on the concept of replay attacks and nonces: http://www.openidenabled.com/openid/replay-attack-prevention

And here's a nonce implementation by Sam Ruby (not a crypto expert by any means, but a smart Ruby dev for sure - the implementation is in Python, though): http://www.intertwingly.net/blog/1585.html

Don't use that code. I had the wrong approach about this, and just saw my mistake. We will need to rethink the approach, probably after review from an expert. I'll be thinking about this in the meantime, and I'll try to at least think a little next time before I start coding.

This is a basic distributed systems problem. There is no way to do a secure shared nothing approach on the client side without creating server side tracking/coordination overhead. I know cookie sessions look simple and appealing at first but we're trading scalable simplicity (db store), for complexity. For security and reliability some sort of token (timestamps or nonces) would have to be coordinated across all server instances and it needs to be perfectly coordinated to work effectively.

http://www.cs.vu.nl/~ast/books/ds1/toc.html (chapters 5, 6, and 8 are good backgrounders)

The downsides of the cookie approach:

*) It causes more lookups/coordination overhead on the server side. You still need a db or distributed transaction coordinator lookup (each server in a cluster needs to know some sort of cluster protocol for token or timestamp management), so we're not saving a db lookup and have to maintain coordinator and recovery logic. *) It puts state unencrypted in a client device we have no control over (open to attack, reduces the usefulness of the store if we can't put anything we want in there and feel safe about it) *) It puts state in an unreliable device (browsers crash - especially firefox with a few add-ons :wink: that would require recovery logic

What is so broken that requires all of this messy overhead and limitations?

In my mind there needs to be an overwhelming pain to justify the cost and I can't see/feel the pain to justify this effort and wasted time on the core list.

SamB

I have a question on this discussion.

Would using Ajax requests throw a wrench into the works?

The example I'm thinking of is:

1. Request 1 hits the server invalidating the nonce for the session, and runs for a bit

2. Request 2 comes in and gets rejected, hence the request fails and something expected to happen doesn't

3. Request 1 completes and a new session cookie is set so that the next action completes successfully.

Thanks,

   ~Wayne

So you actually want to start designing the framework for its _worst_ users? :slight_smile:

Kind regards, Thijs

From a security standpoint, absolutely. Certainly there is some responsibility on the developer's part to learn how the web works, but the framework should be as forgiving as possible to new users.

--be

Here, here!

I did some more work over the weekend and have come up with some other serious problems, which I want to discuss after I've looked into them more.

Koz asked me to try to get a professional review done. To do this, I need two things (preferably from core):

1. A short but precise spec on how the cookie sessions work. The people doing the review may never have seen a line of Ruby in their lives. The spec should be specific and technical, just not assume knowledge of Ruby. It should also be brief, if we want anyone to go over it pro bono.

2. A sample app, with src code and full configuration. It can be simple or even trivial, but it should show how the cookie session is designed to be deployed. But it has to follow the type of use that would be recommended - nothing like "in real life, of course, we'd do it in a more secure way". The configuration and the like are just as important as the app src.

I would be willing to draft a spec if the core team is too busy. We could always run it by them for editing / approval.

be

Perhaps that is your problem. You haven't modelled the login session properly in Rails.

There seems to be an assumption that 'session' handles all this automatically, when in reality it is nothing more than a fancy hash.

A login session exists at a point in time for a particular period of time. It should expire. It is related to a particular user ID (and possibly a browser/IP sequence) and a particular set of transaction sequences. You don't get that with 'session' - you have to model it properly. If you haven't modelled the functionality, then you can't expect to use it.

I suspect the name 'session' is the problem. It's an overloaded concept with a load of built-in expectations. However 'semi_persistent_hash' isn't anywhere as easy to type.

NeilW

The 'standard' use is incorrect. I don't believe it has ever been put forward as 'best practice' by any serious modeller. All I've seen it in is in simplified tutorials. Are you sure it isn't a case of truth by repeated assertion?

You shouldn't be storing ID references to the User model in the session hash; you should be storing ID references to the AuthenticatedSessions model.

That way when you click 'LogOut' the relevant AuthenticatedSession object is deleted from the database, and then it doesn't matter one jot what the next persion does with the cookie.

If something doesn't fit, then look to the model. You've probably missed a relationship that should be a first class model object.

How, exactly, would you model the login session so as to be immune to replay attacks with shared-nothing on the server side?

session[:user_id] is perfectly safe with a server-side session. It is not with a client-side session.

User session expiration has almost nothing to do with this discussion. Expiring user sessions doesn't prevent sessions from being replayed during the validity period. And there's no way to expire user sessions with only a client cookie. We're looking first for a general solution.

I do have a bigger problem with your statement that "If you haven't modelled the functionality, then you can't expect to use it." It is the job of the framework to give developers functionality that they don't have to model. Otherwise Rails would just punt and say "Here's how you set and read a cookie. If you want to use sessions, model them yourself."