13

Background

I'm an experienced web developer (mostly with Python and CherryPy) who has implemented secure session management from scratch before, and is now learning Rails. I'm investigating the behavior of Rails sessions as exposed by the session object that is available in the ActionController instance and view contexts.

Question/Problem

I have read that the default implementation of sessions in Rails 4 uses an encrypted and tamper-proof cookie. Cool, I guess that means I can use it to hold a user ID for user sessions without worrying about session forging (tamper-proof) or anyone being able to find out what their ID is (encrypted). I wanted to test this and see what rails would do if the session cookie was altered.

So, I went and altered the content of the session cookie attribute using a browser add-on, and when I reload the page with the new cookie value, Rails just happily gives me different new values for session_id and _csrf_token.

What happened to session cookie integrity!?

Shouldn't rails detect (via HMAC signature) that the cookie was altered and then tell me about it somehow?

I'm terrified that I'm missing something obscenely obvious, but I've been having no luck searching for an answer on the web, and the source code isn't giving it up easily either (I'm new to ruby). Thanks in advance.

The Experiment

I created a new app and generated a controller with an index action:

$ rails new my_app
$ cd my_app; rails g controller home index

Then I added these two lines to the app/views/layouts/application.html.erb file:

<%= session.keys %><br/>
<%= session.values %>

I started up the dev server and navigated my browser to "localhost:3000/home/index". As expected, the page has the following lines at the bottom:

["session_id", "_csrf_token"]
["8c1558cabe6c86cfb37d6191f2e03bf8", "S8i8/++8t6v8W8RMeyvnNu3Pjvj+KkMo2UEcm1oVVZg="]

Reloading the page gives me the same values, although the app sets a new value of the _my_app_session cookie attribute every time. That seems weird to me, but I'm getting the same session hash values, so I guess it's cool.

Then, I used a cookie editing add-on for Chrome to alter the value of the _my_app_session cookie attribute (replacing the first character of the attribute value). Reloading the page shows completely different values without anything happening. WAT?

4

1 回答 1

14

我不能声称对这里的代码有非常透彻的理解。但我可以告诉你这么多:

我完全按照您的步骤操作(使用 Ruby 2.0.0-p247 和 Rails 4.0),但有一个例外——我还在我的 Gemfile 中添加了“byebug”gem,并在HomeController#index操作中插入了一个调试断点。

在 byebug 控制台中,在该断点处,我可以通过以下方式看到未编辑的会话 cookie:

(byebug) cookies["_my_app_session"]
"cmtWeEc3VG5hZ1BzUzRadW5ETTRSaytIQldiaTMyM0NtTU14c2RrcVVueWRQbncxTnJzVDk3OWU3N21PWWNzb1IrZDUxckdMNmZ0cGl3Mk0wUGUxU1ZWN3BmekFVQTFxNk55OTRwZStJSmtJZVkzVmlVaUI2c2c5cDRDWVVMZ0lJcENmWStESjhzRU81MHFhRTN4VlNWRlJKYTU3aFVLUDR5Y1lSVkplS0J1Wko3R2IxdkVYS3IxTHA2eC9kOW56LS1IbXlmelRlSWxiaG02Q3N2L0tUWHN3PT0=--b37c705a525ab2fb14feb5f2edf86d3ae1ab03c5"

我可以看到实际的加密值

(byebug) cookies.encrypted["_my_app_session"]
{"session_id"=>"13a95fb545a1e3a2d4e9b4c22debc260", "_csrf_token"=>"FXb8pZgmoK0ui0qCW8W75t3sN2KLRpkiFBmLbHSfnhc="}

现在,我通过将第一个字母更改为“A”来编辑 cookie 并刷新页面:

(byebug) cookies["_my_app_session"]
"AmtWeEc3VG5hZ1BzUzRadW5ETTRSaytIQldiaTMyM0NtTU14c2RrcVVueWRQbncxTnJzVDk3OWU3N21PWWNzb1IrZDUxckdMNmZ0cGl3Mk0wUGUxU1ZWN3BmekFVQTFxNk55OTRwZStJSmtJZVkzVmlVaUI2c2c5cDRDWVVMZ0lJcENmWStESjhzRU81MHFhRTN4VlNWRlJKYTU3aFVLUDR5Y1lSVkplS0J1Wko3R2IxdkVYS3IxTHA2eC9kOW56LS1IbXlmelRlSWxiaG02Q3N2L0tUWHN3PT0=--b37c705a525ab2fb14feb5f2edf86d3ae1ab03c5"
(byebug) cookies.encrypted["_my_app_session"]
nil

所以会话nil在请求中的这一点:

(byebug) session
#<ActionDispatch::Request::Session:0x7ff41ace4bc0 not yet loaded>

我可以强制加载会话

(byebug) session.send(:load!)

当我这样做时,我看到生成的会话 ID 是

"f6be13fd646962de676985ec9bb4a8d3"

果然,当我让请求完成时,这就是我在视图中看到的:

["session_id", "_csrf_token"] ["f6be13fd646962de676985ec9bb4a8d3", "qJ/aHzovZYpbrelGpRFec/cNlJyWjonXDoOMlDHbWzg="]

我现在还有一个新的 cookie 值,与我编辑的那个无关。

因此,我认为我们可以得出结论,由于无法验证 cookie 签名,因此会话被无效并重新生成。我现在有一个新会话,使用不同的 csrf_token。

相关代码出现在actionpack/lib/action_dispatch/middleware/cookies.rb:460-464类中EncryptedCookieJar

def decrypt_and_verify(encrypted_message)
  @encryptor.decrypt_and_verify(encrypted_message)
rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
  nil
end

我们不会解密带有无效签名的消息,而是将其视为nil. 因此,存储会话 id 和 csrf 令牌的不可验证 cookie 不会用于加载会话,任何依赖于 cookie 中的值的东西都会失败。

那么为什么我们没有得到一个错误而不仅仅是一个新的会话呢?那是因为我们没有尝试任何依赖于加密值的东西。特别是,虽然我们有

protect_from_forgery with: :exception

(相对于:null_session)中ApplicationController,Rails 不会验证 GET 或 HEAD 请求上的 csrf 令牌——它依赖于开发人员根据规范实现这些操作,因此它们是非破坏性的。如果您在 POST 请求上尝试相同的操作,则会收到ActionController::InvalidAuthenticityToken错误消息(因为您可以轻松地自己验证)。

于 2013-09-12T14:39:49.073 回答