Rails 4.2.4(Devise/Warden)中的无意会话劫持,Phusion Passenger 5.0.24

ald*_*ouw 4 ruby session ruby-on-rails session-hijacking ruby-on-rails-4

背景细节

我们最近遇到了一个问题,其中用户A可能无意中劫持了用户B的会话,该用户B试图在(几乎)与用户A同时访问控制器生成的下载.

我们仍然不能100%确定实现这一目标所需的所有条件,但我们可以在生产和登台环境中可靠地重现问题.这些环境的重要细节如下.

环境细节

Application Server:Phusion Passenger 5.0.21或5.0.24(意思是我们尝试了两个版本并且都重现了这个问题)

框架:Rails 4.2.4

语言:Ruby 2.2.3

操作系统:CentOS 6

有趣的是,我们可以使用重现此问题的Phusion客运4.0.53.

重现劫持的步骤

它可能看起来太简单了,但这就是必要的.

  1. 用户A登录系统
  2. 用户B登录系统
  3. 用户A和B都在(几乎)同时快速单击相同的下载按钮

这就是某人的会话被无意中劫持所需要的一切.(看起来轮盘赌A或B的会话是否被劫持,虽然它可能不像看起来那么随机.)

我们知道用户的会话已被劫持,因为我们可以看到页面上显示的当前会话的用户名和姓.

每次,一个用户"成为"另一个用户.

如果用户访问角色不同,则还意味着您现在可能具有不同的访问级别.例如,有人可能突然成为管理员,如果这是他们无意中被劫持的会话....

代码要求

最初似乎Phusion Passenger是导致此问题的唯一因素,因为当我们切换回版本4时,此问题不再出现.

在一些代码改变之后,我们确定我们的控制器代码中的方法似乎导致了这个问题的发生.

这是一个示例Controller方法,它将在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  
Run Code Online (Sandbox Code Playgroud)

似乎我们对Cache-Control的修改可能已经很好地解决了这个问题.

也许我们不应该对此进行修改,但我们希望有人可能会深入了解缓存控制参数如何能够突然让我们陷入不同的会话.

为了测试这个,你必须有一个映射到Controller#sample_method的路由,你必须有一个按钮可以点击下载这个文件.

我意识到我们正在指定我们想要一个CSV并且没有返回CSV,但在这种情况下我用proc替换了我们的实际CSV,因为我们的CSV是在一个单独的类中生成的.

上面列出的环境中的上述代码将重现该问题.

其他依赖性

我们使用Devise gem进行用户身份验证.如果您要设置测试应用程序以尝试重现此问题,则需要设置Devise和两个帐户.

顺便说一句,你还需要在两台独立的计算机上安排两个人来测试它.您既需要同时登录系统,也要同时尝试多次点击该按钮.

我意识到这个问题似乎很牵强,但它确实在我们的环境中表现出来.它需要特定版本的Phusion Passenger,一组特定的标题和一个渲染块才能实现,但它确实发生了.(具体代码列在"需要代码"部分中.)

修复

好消息是有一种解决这个问题的方法.我们能够在format.csv块中使用#send_data方法.

而不是其他代码块,我们只是在这些方面做一些事情:

  format.csv {
    send_data data_here, filename: filename, type: 'text/csv', disposition: 'attachment'
  }
Run Code Online (Sandbox Code Playgroud)

这是更清晰的代码和更好的代码.但是我们仍然担心存在某种更大的问题 - 无论是在Passenger中,还是在我们的代码本身中.

想法?

也许社区中的专家可以解释像这样无意的会话如何被劫持是可能的.

似乎会话cookie可能没有正确地来回发送.(我们没有将数据库用于我们的会话.)

虽然我们已经针对这个问题的特定实例进行了修复,但我们不确定是否存在其他潜在问题(可能在Passenger中?),这些问题首先会显示出来.

这似乎是一个非常奇怪的问题.

另一方面,也许只是我们用标题做的事情是个坏主意.

您的见解表示赞赏!

Fre*_*ung 6

您的缓存控制语句允许缓存(它强制重新验证,即浏览器/缓存不会直接从缓存提供请求,但不会停止返回缓存的响应),而默认缓存控制标头rails emit包含'private',它不允许中间代理进行缓存(仍然允许浏览器缓存).

鉴于响应可能包括rails会话cookie,缓存该响应并将其重新用于另一个用户导致第二个用户从第一个用户获取cookie.即使您使用的是数据库支持的会话存储,您仍然可以获取cookie来识别要使用的数据库中的哪一行.每当您显示私有内容时,您都需要非常小心缓存标头.

乘客版本相关的原因是乘客5包括http缓存层.您的错误仍然存​​在于乘客4中,更难以触发(例如,公司代理后面的2个用户).

您几乎肯定会将您的响应标记为私有,这意味着中间缓存(包括乘客中的缓存)不会缓存响应.Phusion撰写了一篇博文,更详细地描述了这一点.您也可以完全关闭turbocaching - 假设默认情况下rails将所有响应标记为私有,它可能无论如何都不会对您的应用程序执行任何有用的操作.

  • 我非常喜欢Passenger,但我认为turbocache功能是一个错误,至少在默认情况下是这样.我会把它关掉,除非我明白它在做什么并知道我想要它.来自Passenger发行说明:>默认情况下已启用,但您可以使用--disable-turbocaching(Standalone),PassengerTurbocaching off(Apache)或passenger_turbocaching off(Nginx)禁用它. (2认同)