Session IDs & naming footguns

There is an issue our team identified that is worth filing a bug for (or at least, we think it’s an issue), but it’s non-obvious what project to which it should be filed, so I’m asking here.

Background on sessions in Rails

When building a Rails application, you will likely need to store user session state, which the framework has a built-in mechanism to handle. Depending on product requirements, you can pick from a few stores for said session (quoting the ActionController docs):

[…]

For most stores, this ID is used to look up the session data on the server, e.g. in a database table. There is one exception, and that is the default and recommended session store - the CookieStore - which stores all session data in the cookie itself (the ID is still available to you if you need it). […].

What is an ID in Rails?

The notion of an “ID” has a fairly consistent meaning throughout Rails: an opaque identifier that can be used to identify a resource. IDs are shared in various places that one would normally not put sensitive information: URLs, logs, error messages, etc. Getting an ID is never enough to get

What is the problem?

Session stores besides CookieStore make session IDs violate the common understanding of an “ID” in Rails. While Rails famously provides collection of sharp knives, this is more of a misunderstanding-in-the-making than a sharp tool in and of itself. Making things worse, the notion of session IDs being dangerous (while noted at length in the docs), are only dangerous for stores other than the default. Meaning, one can make many references to a session ID in insecure contexts without issue if using the default store, but changing your store suddenly makes all of those references highly problematic.

Even worse, the various patches that addressed CVE-2019-16782 made things more confusing, introducing the notion of a “session public ID” and “session private ID”. One could very reasonably assume that a “public ID” is non-sensitive/freely shareable while “private ID” is an important secret, as is the case in asymmetric cryptography (commonplace in web-dev). In reality, unless you have direct session store access (e.g. access to your relational database of choice if using ActiveRecordStore), the reverse is true: a “public ID” is the dangerous secret value while the “private ID” is mostly harmless.

Suggested solution

Change the mechanics of stores (besides CookieStore) to have both a session ID (which becomes the value of an opaque identifier that is harmless for impersonation on its own) and a session token (which is the important secret value). The public vs. private distinction could be similarly migrated to be named “token” vs. “token digest” or something similar.

4 Likes

In our current Rails app we store sessions in Postgres, but we never just accept the id from the client.

For front-end web, we keep it in a signed cookie, so id won’t be valid if signature is incorrect.

For API clients, we use signed id as the token, and look it up via find_signed, so again, it won’t be valid if it’s not properly signed.

So maybe I’m misunderstanding, are you saying that the id is not supposed to be considered public because some people can accept it from a client without verifying any kind of signature?

That seems totally reasonable, but that’s not offered out of the box as far as I can tell with Rails. I’m assuming you added some application code to achieve that? Active Record Session Store does not sign nor encrypt cookies by default, nor is there a config option to do so (though there is an outstanding PR from a few years back). If that PR were merged (with the addition of signed and/or encrypted cookies being the default), the problem would also be addressed.

Wow, this seems like a serious issue, great find. We indeed used our own session model and controller logic, and now I’m glad we did.

I think the id itself doesn’t need to be kept secret, but it should never be accepted without some cryptographic authentication (i.e. signature verification). I vote to focus on this part instead.

1 Like

If I am reading all of this correctly; activerecord-store uses a cookie to store the ID of the record storing the session data. It’s also apparently not signed or encrypted and thus not secure.

CookieStore uses a cookie to store whatever you want and is signed and/or encrypted and thus secure.

If you need to store more than what you can store in a Cookie, you can achieve the same thing as said gem by creating your own Session model, and simply storing the ID of this inside the cookie using CookieStore. This is signed and/or encrypted, and gives you your AR storage layer. This also prevents you from depending on another gem. This gem really isn’t doing much… besides removing the security of CookieStore.

I think the main issue here is that there’s a gem under /rails GitHub account (seems to be blessed by the Rails team, and the go-to way of using database for session storage) that if you use exactly as instructed, would leave you with a pretty bad security vulnerability. It would allow any user to become any other user, by simply swapping the plain text session id in their cookie.

I wonder how many websites are currently live that are using activerecord-session_store gem as instructed, and are vulnerable to this.

2 Likes