4

我正在编写一个带有 AngularJS 前端的 rails 应用程序,这是我正在编写的关于连接 rails 和 angularjs的教程系列的一部分。这意味着我的 Rails 应用程序仅以 JSON 格式与浏览器通信。

angularjs $http 文档中,它描述了一个潜在的 json 安全漏洞,其中 json 请求可以嵌入到脚本标签中,加上一些棘手的 jsonp 使用,以允许类似于跨站点脚本攻击的东西。我发现了其他几页,特别是我认为描述得很好的一页,并且可以追溯到 2008 年,所以这不是一个新问题。

显然,这不是标准 rails json 渲染中的漏洞,因为 rails 默认提供包含数组的对象。但是在使用 angularjs 时,我们似乎设置了 root: false (虽然我不得不承认我找不到我这样做的地方,但它绝对没有给出根节点)。

无论如何,底线是角度文档建议在任何 json 响应前面加上 )]}',所以:

['one','two']

变成

)]}',
['one','two']

然后,Angular 会再次自动将其剥离。

我正在寻找一种优雅地做到这一点的方法。我在 stackoverflow 上看到了很多关于此的问题和答案,但其中大多数要么与更早版本的 rails 相关,在 JSON 处理被更彻底地嵌入之前,要么似乎需要我创建大量样板代码。我正在寻找一种可以应用于应用程序控制器的方法,或者作为一种辅助方法,它可以在任何地方工作。

我目前使用的控制器如下所示:

class ClubsController < ApplicationController
  respond_to :json

  # GET /clubs.json
  def index
    @clubs = Club.all        
    render json: @clubs
  end
end

这不会调用任何模板 - 渲染操作会跳过模板引擎。我可以通过将渲染线改为:

respond_with json: @clubs

并创建一个模板文件views/clubs/index.json.erb,其中包含

)]}',
<%= raw(@clubs.to_json) %>

但是我必须为每个控制器上的每个动作创建一个模板,这感觉就像样板。我希望能够将视图/布局/application.json.erb 更改为:

)]}',
<%= yield %>

但这不起作用,因为我们只有在调用 respond_with 时才能得到模板。如果我们调用respond_with,我们无法将@clubs 放入响应中——所以我们最终得到:

)]}',

作为整个响应。

另一种选择可能是覆盖 as_json 方法来添加我想要的东西,但这似乎有点像大锤。理想情况下,我可以在某个地方引入辅助方法,例如:

render prepend_vulnerability_protection(json: @clubs)

所以,毕竟,有两个问题:

  1. 这甚至是一个真正的问题,还是 Rails 已经有了其他保护,这意味着我根本不需要担心这个
  2. 有没有办法集中执行此操作,还是我需要硬着头皮创建所有样板模板?我可以修改脚手架生成器来做到这一点,所以这不是世界末日,但它看起来确实像很多样板代码
4

1 回答 1

4

所以,目前还没有回应。我将写下我从研究中发现的内容以及我当前的答案。

首先,我认为这是 Rails 中真正的漏洞。不幸的是,Rails 和 JSON/JSONP 领域最近出现了一些与 Rails 端的 JSON 解析器相关的其他漏洞。这确实淹没了与这个特定 XSS 问题相关的任何谷歌搜索。

有几种方法可以解决这个问题:

  1. 让您的应用程序仅响应 put/post/delete 请求。在集成到 Angular 时,这并不是一个真正的选择——嗯,它是,但这意味着覆盖一堆标准行为
  2. 在返回的 JSON 的前面插入一些东西 - 这可以是根节点(rails 3 中的默认 rails 行为,不再在 3.1 中),像 )]}; 这样的闭包,或者像 while (1); 这样的循环。Angular 期望并且可以处理 )]}',

我已经研究过在我的 rails 应用程序中使用 json 模板。您可以使用许多 gem 中的一种来做到这一点,我喜欢 JBuilder(railscast 320)的外观,但 RABL 可能更强大(railscast 322)。

这确实意味着每个控制器上的每个操作都有一个模板。但是,我也刚刚完成了如何让rails 为我自动搭建这些脚手架的工作,所以它不像我第一次问这个问题时那么可怕,而且我可以看到一些其他原因,我可能想要更多地控制从我的应用程序返回的 json。

话虽如此,我无法立即看到让 JBuilder 预先添加任意字符串的方法 - 它似乎只想准备有效的 JSON(我认为这不是有效的 JSON)。RABL 看起来可以做到,但它有点复杂。绝对可以通过仅使用 ERB 来完成,但我觉得这样做有点不对。

我确定的另一个替代方法是 application_controller.rb 中的辅助方法,然后我在每个控制器方法中调用它。这是相当优雅的,我可以很容易地改变我的模板来做到这一点。所以我现在要这样做:

class ApplicationController < ActionController::Base
  def render_with_protection(json_content, parameters = {})
    render parameters.merge(content_type: 'application/json', text: ")]}',\n" + json_content)
  end
end

class ClubsController < ApplicationController
  respond_to :json

  # GET /clubs.json
  def index
    @clubs = Club.all

    render_with_protection @clubs.to_json
  end

  # GET /clubs/1.json
  def show
    @club = Club.find(params[:id])

    render_with_protection @club.to_json
  end

  # POST /clubs.json
  def create
    @club = Club.new(params[:club])

    if @club.save
      render_with_protection @club.to_json, {status: :created, location: @club}
    else
      render_with_protection @club.errors.to_json, {status: :unprocessable_entity}
    end
  end
end

请注意,您还应该在应用程序控制器中包含 CSRF 保护 - 所以将其视为您已经采取的安全预防措施的补充,而不是替代。

于 2013-09-27T08:33:33.890 回答