背景详细信息
我们最近遇到了一个问题,用户a可能会无意中劫持用户b的会话,该用户试图在(几乎)与用户a同时访问控制器生成的下载。
我们仍然不能百分之百确定发生这种情况所必需的所有条件,但我们可以在生产和准备环境中可靠地重现问题。这些环境的重要细节如下。
环境详细信息
应用服务器:phusion passenger 5.0.21或5.0.24(这意味着我们尝试了两个版本,并且都复制了这个问题)
框架:Rails 4.2.4
语言:ruby 2.2.3
操作系统:CentOS 6
有趣的是,我们不能用Phusion Passenger 4.0.53再现这个问题。
重现劫持的步骤
这似乎太简单了,不可能是真的,但这是所有必要的。
用户A登录系统
用户b登录系统
用户A和B都在(几乎)同一时间快速点击同一个下载按钮
这就是某人的会话被无意劫持所需的全部时间。(关于A或B的会话是否被劫持,这看起来像是轮盘赌,尽管它可能不像看起来那么随机。)
我们知道用户的会话被劫持了,因为我们可以在页面上看到当前会话的用户名和名。
每次,一个用户“变成”另一个用户。
如果用户访问角色不同,也意味着您现在可能拥有不同级别的访问。例如,如果某个人无意中劫持了会话,他可能会突然成为管理员…
需要代码
最初看起来,phusion passenger是导致这个问题的唯一原因,因为当我们切换回版本4时,这个问题不再出现。
但是,在一些代码更改之后,我们确定控制器代码中的一个方法似乎导致了这个问题的发生。
下面是一个示例控制器方法,可以在Phusion Passenger 5.0.21或5.0.24上生成此问题:
def sample_method
respond_to do |format|
format.csv {
headers.merge!({'Cache-Control'=>'must-revalidate, post-check=0, pre-check=0'})
render :text => proc { |response, output|
100.times do |i|
output.write("This is line #{i}\n")
end
}
}
end
end
我们对缓存控制的修改似乎对这个问题起到了很好的作用。
也许我们不应该修改这个,但是我们希望有人能够洞察缓存控制参数是如何能够突然让我们进入另一个会话的。
为了测试这个,您必须有一个映射到controller sample_方法的路由,并且您必须有一个可用的按钮来单击以下载这个文件。
我意识到我们正在指定我们想要一个csv而不是返回一个csv,但是在本例中我用proc替换了实际的csv,因为我们的csv是在一个单独的类中生成的。
上面列出的环境中的上述代码将重现此问题。
其他依赖项
我们正在使用desve gem进行用户身份验证。如果要设置一个测试应用程序来尝试重现此问题,则需要devise和两个帐户设置。
顺便说一下,你还需要两个人在两台独立的电脑上测试这个。你们两个都需要同时登录到系统中,并尝试同时点击按钮很多次。
我意识到这个问题似乎牵强,但它确实在我们的环境中显现出来。它需要特定版本的phusion passenger、特定的头集和呈现块才能发生,但它确实发生了。(具体代码列在“代码要求”部分。)
修复
好消息是有办法解决这个问题的代码。我们能够在format.csv块中使用“send”数据方法。
与其他代码块不同的是,我们只是沿着以下几行做一些事情:
format.csv {
send_data data_here, filename: filename, type: 'text/csv', disposition: 'attachment'
}
这是更干净的代码和更好的代码。但我们仍然担心会有一些更大的问题——无论是在乘客身上,还是在我们的代码中。
思想?
也许社区中的专家可以解释这种无意的会话劫持是如何可能的。
似乎会话cookie没有正确地来回发送。(我们的会话不使用数据库。)
虽然我们已经解决了这个问题的特殊情况,但我们不确定是否还有其他潜在的问题(可能是乘客的问题?)这使得这个问题首先显现出来。
这似乎是一个很奇怪的问题。
另一方面,也许只是我们用头球做的是个坏主意。
感谢您的真知灼见!
最佳答案
您的cache control语句允许缓存(它强制重新验证,即浏览器/缓存不会直接从缓存提供请求,但不会停止返回缓存响应)。而rails发出的默认缓存控制头包含“private”,不允许通过中介代理进行缓存(仍然允许浏览器缓存)。
考虑到响应可能包括rails会话cookie,缓存该响应并将其重用给另一个用户会导致第二个用户从第一个用户获取cookie。即使您使用的是数据库支持的会话存储,您仍然会得到cookie来标识数据库中要使用的行。任何时候显示私有内容时,都需要非常小心地缓存头。
乘客版本相关的原因是乘客5包括http缓存层。你的错误仍然存在于乘客4中,只是很难触发(例如,公司代理背后的2个用户)。
几乎可以肯定,您应该将响应标记为private,这意味着中介缓存(包括passenger中的缓存)不会缓存响应。phusion写了一个blog post详细描述了这一点。你也可以完全关闭TurboCaching,因为在默认情况下,Rails会将所有响应标记为私有,所以无论如何,它在你的应用程序中可能不会做任何有用的事情。
关于ruby-on-rails - Rails中的意外 session 劫持4.2.4(Devise/Warden),Phusion乘客5.0.24,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/35234161/