安全 Rails sanitize allowed_attributes 不安全

Rei · 2016年01月17日 · 最后由 linyunjiang 回复于 2016年03月16日 · 8953 次阅读

当需要输出用户的 HTML 内容的时候,必须使用 sanitize 方法:

<%= sanitize body %>

sanitize 方法可以设置 tags 和 attributes 白名单:

<%= sanitize body, tags: %w(a), attributes: %w(href) %>

但是 attributes 白名单不安全,根据现在的实现(#issues-27),如果设置了 attributes 参数,就会跳过一些属性过滤,例如协议过滤:

sanitizie '<a href="javascript:alert()">click</a>'
#=> <a>click</a>

sanitizie '<a href="javascript:alert()">click</a>', attributes: %w(href)
#=> <a href="javascript:alert()">click</a>'

设置 config.action_view.sanitized_allowed_attributes 是同样的效果。

解决方法:

  1. 使用 sanitize 不加参数,这时候会允许一大堆标签和属性,但是是安全的(大概……)。
  2. 使用两次 sanitize,先设置白名单,再安全过滤:sanitize sanitize(body, tags: TAGS_ALLOWED, attributes: ATTRIBUTES_ALLOWED)
  3. 更严格的场景,推荐使用 https://github.com/rgrove/sanitize ,可以精确过滤标签、属性,甚至协议类型、CSS 属性……。

题外话:

其实两年前 @jasl 就提了这个问题,我现在才看懂…… https://ruby-china.org/topics/16633#reply3


新版已经修复了

https://github.com/rails/rails-html-sanitizer/commit/297161e29a3e11186ce4c02bf7defc088bf544d4

已更新为第二种方法,谢谢 rei

白名单没有考虑属性的内容,如果有需求,可以自定义 scrubber,不需要引入额外的 gem

sanitize(html, scrubber: CustomPermitScrubber.new)
class CustomPermitScrubber < Rails::Html::PermitScrubber
  def scrub(node)
    node.remove if node.name == 'a' && node["href"].present? && !node["href"].start_with?('http')
    super
  end
end

#3 楼 @quakewang 我觉得把这段

def scrub_attributes(node)
  if @attributes
    node.attribute_nodes.each do |attr|
      attr.remove if scrub_attribute?(attr.name)
    end

    scrub_css_attribute(node)
  else
    Loofah::HTML5::Scrub.scrub_attributes(node)
  end
end

改成这样就好了:

def scrub_attributes(node)
  if @attributes
    node.attribute_nodes.each do |attr|
      attr.remove if scrub_attribute?(attr.name)
    end
  end
  Loofah::HTML5::Scrub.scrub_attributes(node)
end

Loofah::HTML5::Scrub 里面还有不少规则,也不知道以后会再加什么规则,全部继承才能利用到。

然后看了 sanitize,觉得这个 gem 规则设置要容易一些。

#4 楼 @rei 嗯,这样改更好,提个 PR 看看他们会不会收

@rei 我使用了https://github.com/rgrove/sanitize进行白名单过滤,但如果再用 sanitize Sanitize.fragment(..) 来显示的话,还是有一些样式会丢失,这时如果用 raw Sanitize.fragment(..) 这样显示是正常的,但不清楚这样是否安全?

需要 登录 后方可回复, 如果你还没有账号请 注册新账号