代码之家  ›  专栏  ›  技术社区  ›  aldefouw

铁路4.2.4中的非故意会话劫持(Devise/Warden),Phusion乘客5.0.24

  •  5
  • aldefouw  · 技术社区  · 9 年前

    背景详细信息

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

    我们仍然不能100%确定发生这种情况所需的所有条件,但我们可以在我们的生产和舞台环境中可靠地重现问题。这些环境的重要细节如下。

    环境详细信息

    应用程序服务器: 输液乘客5.0.21或5.0.24 (这意味着我们尝试了两个版本,都复制了该问题)

    框架: 钢轨4.2.4

    语言: 红宝石2.2.3

    操作系统: 中央操作系统6

    有趣的是,我们可以 不是 使用重现此问题 输液乘客4.0.53 .

    再现劫持的步骤

    这似乎太简单了,不可能是真的,但这就是所有必要的。

    1. 用户A登录系统
    2. 用户B登录系统
    3. 用户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_method的路由,并且您必须有可以单击以下载此文件的按钮。

    我意识到我们正在指定我们想要CSV而不是返回CSV,但我在本例中用proc替换了实际的CSV,因为我们的CSV是在单独的类中生成的。

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

    其他依赖关系

    我们正在使用 设计宝石 用于用户身份验证。如果您要设置一个测试应用程序来尝试重现这个问题,您需要设置Devise和两个帐户。

    顺便说一句,您还需要两个人在两台独立的计算机上进行测试。你们两个都需要同时登录到系统,同时尝试多次点击按钮。

    我意识到这个问题似乎很牵强,但它确实在我们的环境中表现出来了。它需要特定版本的Phusion Passenger、一组特定的头文件和一个渲染块才能实现,但事实确实如此。(具体代码列在 所需代码 第节)

    修复

    好消息是,代码可以解决这个问题。我们能够在格式中使用#send_data方法。csv块。

    我们只是沿着以下几行做一些事情,而不是其他代码块:

      format.csv {
        send_data data_here, filename: filename, type: 'text/csv', disposition: 'attachment'
      }
    

    这是更干净的代码和更好的代码。但我们仍然担心有某种更大的问题——无论是在Passenger中,还是在我们的代码本身中。

    思想?

    也许社区中的一位专家可以解释这样的无意会话劫持是如何发生的。

    似乎会话cookie没有正确地来回发送。(我们没有在会话中使用数据库。)

    尽管我们已经解决了这个特定的问题,但我们不确定是否存在其他潜在问题(可能是在乘客?)这使得这个问题首先表现出来。

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

    另一方面,也许只是我们的头球是个坏主意。

    感谢您的见解!

    1 回复  |  直到 9 年前
        1
  •  6
  •   Frederick Cheung    9 年前

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

    考虑到响应可能包括rails会话cookie,缓存该响应并将其重新用于另一用户会导致第二用户从第一用户获取cookie。即使您使用的是数据库支持的会话存储,您仍然会得到cookie来标识要使用数据库中的哪一行。无论何时显示私有内容,都需要非常小心地缓存标题。

    乘客版本相关的原因是乘客5包括http缓存层。你的bug仍然存在于乘客4中,只是很难触发(例如,公司代理后面的2个用户)。

    几乎可以肯定,您应该将响应标记为私有,这意味着中间缓存(包括乘客中的缓存)不会缓存响应。Phusion写了一篇 blog post 更详细地描述了这一点。你也可以完全关闭涡轮增压-考虑到默认情况下,rails将所有响应标记为私有,它很可能不会在你的应用程序中做任何有用的事情。