brunofacca/zen-rails-security-checklist

GitHub: brunofacca/zen-rails-security-checklist

一份覆盖认证、注入、XSS、CSRF等核心安全领域的Ruby on Rails应用开发安全检查清单。

Stars: 1817 | Forks: 144

# Zen Rails 安全检查清单 ## 概述 本文档提供了一份开发 Ruby on Rails 应用程序时应实施的安全措施清单(不一定全面)。它旨在作为快速参考,以减少因开发人员疏忽导致的漏洞。它不能替代有关安全编码原则及其应用方法的开发人员培训。 描述每种安全漏洞的工作原理超出了本文档的范围。检查清单相应部分提供了包含更多详细信息的外部资源链接。请仅应用您完全理解的建议。 请记住,安全是一个不断变化的目标。每天都会发现新的漏洞和攻击媒介。我们建议您尽量保持最新状态,例如,通过订阅与您正在使用的软件和库相关的安全邮件列表。 本检查清单旨在成为一项社区驱动的资源。欢迎您的[贡献](#contributing)! **免责声明**:本文档并未涵盖所有可能的安全漏洞。作者对本文信息的准确性或完整性不承担任何法律责任。 ## 支持的 Rails 版本 本文档侧重于 Rails 4 和 5。不包含在早期版本中存在并在 Rails 4 中已修复的漏洞。 ## 目录 - [检查清单](#the-checklist) - [注入](#injection) - [身份验证](#authentication) - [会话与 Cookies](#sessions--cookies) - [跨站脚本攻击 (XSS)](#cross-site-scripting-xss) - [处理用户输入](#handling-user-input) - [输出转义与清理](#output-escaping--sanitization) - [HAML 模板中的 XSS 防护](#xss-protection-in-haml-templates) - [内容安全策略 (CSP)](#content-security-policy-csp) - [不安全的直接对象引用](#insecure-direct-object-reference) - [HTTP 与 TLS](#http--tls) - [安全相关的请求头](#security-related-headers) - [Memcached 安全](#memcached-security) - [授权 (Pundit)](#authorization-pundit) - [文件](#files) - [文件上传](#file-uploads) - [文件下载](#file-downloads) - [跨站请求伪造 (CSRF)](#cross-site-request-forgery-csrf) - [跨域资源共享 (CORS)](#cross-origin-resource-sharing-cors) - [敏感数据泄露](#sensitive-data-exposure) - [凭证](#credentials) - [路由、模板选择与重定向](#routing-template-selection-and-redirection) - [第三方软件](#third-party-software) - [安全工具](#security-tools) - [测试](#testing) - [其他](#others) - [详细信息和代码示例](#details-and-code-samples) - [命令注入示例](#command-injection-example) - [密码验证正则表达式](#password-validation-regex) - [Pundit:确保所有操作均已授权](#pundit-ensure-all-actions-are-authorized) - [Pundit:在选择框中仅显示适当的记录](#pundit-only-display-appropriate-records-in-select-boxes) - [rack-cors 配置](#rack-cors-configuration) - [将 filter_parameters 转换为白名单](#convert-filter_parameters-into-a-whitelist) - [请求限流](#throttling-requests) - [HAML: XSS 防护](#haml-xss-protection) - [作者](#authors) - [贡献](#contributing) - [TODO](#todo) - [参考资料与延伸阅读](#references-and-further-reading) - [许可证](#license) 目录由 [DocToc](https://github.com/thlorenz/doctoc) 生成。 ## 检查清单 #### 注入 注入攻击在 [OWASP Top10](https://www.owasp.org/index.php/Top_10_2013-Top_10) 中排名第一。 - [ ] 不要使用标准的 Ruby 插值 (`#{foo}`) 将用户输入的字符串插入到 ActiveRecord 或原始 SQL 查询中。使用 `?` 字符、命名绑定变量或 [ActiveRecord::Sanitization 方法](http://api.rubyonrails.org/classes/ActiveRecord/Sanitization/ClassMethods.html#method-i-sanitize_conditions) 来清理数据库查询中使用的用户输入。*缓解 SQL 注入攻击。* - [ ] 不要将用户输入的字符串传递给能够评估代码或运行操作系统命令的方法,例如 `eval`、`system`、`syscall`、`%x()`、`open`、`popen`、`File.read`、`File.write` 和 `exec`。使用正则表达式是清理它的好方法([代码示例](#command-injection-example))。*缓解命令注入攻击。* 资源: - [Ruby on Rails 安全指南 - SQL 注入](http://guides.rubyonrails.org/security.html#sql-injection) - [Rails SQL 注入示例](https://rails-sqli.org/) - [加强 Rails 应用的安全性 | 第 1 部分](https://www.ombulabs.com/blog/rails/security/rails-security.html) #### 身份验证 失效的身份验证和会话管理在 [OWASP Top 10](https://www.owasp.org/index.php/Top_10_2013-Top_10) 中排名第二。 - [ ] 除非您**确切**知道自己正在做什么,否则避免自行编写身份验证逻辑。考虑使用诸如 [Devise](https://github.com/plataformatec/devise)、[Authlogic](https://github.com/binarylogic/authlogic) 或 [Clearance](https://github.com/thoughtbot/clearance) 等 gem。*缓解数十种潜在的漏洞。* - [ ] 强制要求密码长度至少为 8 个字符或以上。*缓解暴力破解攻击。* - Devise:在 `config/initializers/devise.rb` 中设置 `config.password_length = 8..128`。 - [ ] 考虑针对以下情况验证密码: - 字典词。由于密码有最小长度要求,因此字典只需包含满足该要求的单词。 - 常用密码列表,例如[这些](https://github.com/danielmiessler/SecLists/tree/master/Passwords)。[StrongPassword](https://github.com/bdmac/strong_password) gem 提供了此类功能。 - 泄露的密码数据库,例如 [PasswordPing](https://www.passwordping.com/docs-passwords-api/)。 - 与上下文相关的词汇,例如应用程序名称、用户名及其衍生词。 - [ ] 考虑强制执行密码复杂性规则(例如混合使用不同字符类型)的优缺点。大多数应用程序都使用它。然而,最新的 [NIST 指南](https://pages.nist.gov/800-63-3/sp800-63b.html) 建议不要这样做。一种替代方法是增加最小长度要求并鼓励使用密码短语。*缓解暴力破解攻击。* - Devise:使用 Devise 专用的 gem,例如 [devise-security](https://github.com/devise-security/devise-security)、[devise_zxcvbn](https://github.com/bitzesty/devise_zxcvbn) 或以下与身份验证无关的解决方案之一。 - [StrongPassword](https://github.com/bdmac/strong_password) gem 或正则表达式验证([代码示例](#password-validation-regex))应该适用于大多数身份验证设置。 - [ ] 在多次登录尝试失败后锁定帐户。*缓解暴力破解攻击。* - Devise:激活 [lockable module](https://github.com/plataformatec/devise/wiki/How-To:-Add-:lockable-to-Users)。 - [ ] 要求用户在注册时和电子邮件地址更改时确认其电子邮件地址。*缓解使用不存在或第三方的电子邮件创建虚假帐户。* - Devise:使用 [confirmable module](https://github.com/plataformatec/devise/wiki/How-To:-Add-:confirmable-to-Users) 并在 `config/initializers/devise.rb` 中设置 `config.reconfirmable = true`。 - [ ] 要求用户在更改密码时输入旧密码。*缓解在会话劫持、CSRF 或用户忘记注销且让 PC 或移动设备处于无人看管状态时的未经授权密码更改。* - Devise:默认执行此操作 - [ ] 在注销时使会话过期,并在每次成功登录时使旧会话过期。*通过减少时间窗口来缓解 CSRF、会话劫持和会话固定攻击。* - Devise:默认执行此操作。 - [ ] 在一段不活动时间(例如 30 分钟)后使会话过期。*通过减少时间窗口来缓解 CSRF、会话劫持和会话固定攻击。* - Devise:使用 [timeoutable module](http://www.rubydoc.info/github/plataformatec/devise/Devise/Models/Timeoutable)。 - [ ] 在密码更改时通过电子邮件通知用户。*不能防止攻击者更改受害者的密码,但会警告受害者,以便他可以联系系统管理员撤销攻击者的访问权限。* - Devise:在 `config/initializers/devise.rb` 中设置 `config.send_password_change_notification = true`。 - [ ] 使用诸如“Invalid email or password”之类的通用错误消息,而不是具体指出哪一部分(电子邮件或密码)无效。*缓解[用户枚举](https://www.owasp.org/index.php/Testing_for_user_enumeration_(OWASP-AT-002)) 和暴力破解攻击。* - Devise:在 `config/initializers/devise.rb` 中设置 `config.paranoid = true` 将保护 `confirmable`、`recoverable` 和 `unlockable` 模块免受用户枚举。为了保护 `registerable` 模块,请在注册页面添加验证码(参见 [Devise Wiki 中的说明](https://github.com/plataformatec/devise/wiki/How-To:-Use-Recaptcha-with-Devise)))。 - [ ] 确保所有非公共控制器/操作都需要身份验证。*避免因开发人员疏忽导致的未经授权访问。* - Devise:将 `before_action :authenticate_user!` 添加到 `ApplicationController`,并将 `skip_before_action :authenticate_user!` 添加到可公开访问的控制器/操作。 - [ ] 考虑使用由 [Authy](https://www.authy.com/) 提供的双因素身份验证 (2FA)。*提供极其有效的额外身份验证安全层。* - Devise:参见 [devise-two-factor](https://github.com/tinfoil/devise-two-factor) 和 [authy-devise](https://github.com/authy/authy-devise) gem。 - [ ] 考虑在 `config/routes.rb` 中要求身份验证。在控制器和路由中同时要求身份验证可能不够 DRY,但这种冗余提供了额外的安全性(参见 [深度防御](https://en.wikipedia.org/wiki/Defense_in_depth_(computing)))。 - Devise:将非公共资源放置在 `authenticate :user do` 块中(参见 [Devise Wiki](https://github.com/plataformatec/devise/wiki/How-To:-Define-resource-actions-that-require-authentication-using-routes.rb))。 - [ ] 考虑限制每个帐户的同时会话数。*可减少帐户泄露(例如密码泄露)时的应用程序暴露。* - Devise:使用 [devise-security](https://github.com/devise-security/devise-security) gem。 - [ ] 避免实施诸如“What is your mother's maiden name?”之类的“安全问题”,因为其答案可能会在多个网站上重复使用,并且很容易通过[社会工程学](https://en.wikipedia.org/wiki/Social_engineering_(security)) 找到。请参阅[这篇文章](https://www.wired.com/2016/09/time-kill-security-questions-answer-lies/)。 - [ ] 如果使用基于角色的访问控制 (RBAC),请不要在用于用户注册和个人资料编辑的控制器 strong parameters 中包含 role 属性。*防止恶意用户为自己分配管理员角色。* - Devise:不要将 role 参数键传递给 `devise_parameter_sanitizer.permit`。 - [ ] 考虑通过 IP 限制管理员访问。如果客户端的 IP 是动态的,则通过 IP 块/ASN 或通过 IP 地理定位按国家/地区进行限制。 #### 会话与 Cookies 失效的身份验证和会话管理在 [OWASP Top 10](https://www.owasp.org/index.php/Top_10_2013-Top_10) 中排名第二。 - [ ] 不要在 cookie 或 CookieStore Session 中存储诸如金钱/积分余额或用户权限之类的数据。应将其存储在数据库中。*缓解重放攻击。* - [ ] 考虑始终使用加密的 cookie。在设置 `secret_key_base` 时,这是 Rails 4+ 的默认行为。*加强 cookie 加密并缓解涉及 cookie 篡改的多种攻击。* - [ ] 除非您的 JavaScript 前端需要读取由 Rails 服务器生成的 cookie,否则请将所有 cookie 设置为 `httponly`。在项目中搜索 cookie 访问器并添加 `httponly: true`。例如:`cookies[:login] = {value: 'user', httponly: true}`。*将 cookie 访问限制为 Rails 服务器。缓解攻击者在成功进行 XSS 攻击后利用受害者的浏览器 JavaScript 窃取 cookie。* 资源: - [Ruby on Rails 安全指南 - 会话](http://guides.rubyonrails.org/security.html#sessions) #### 跨站脚本攻击 (XSS) XSS 在 [OWASP Top 10](https://www.owasp.org/index.php/Top_10_2013-Top_10) 中排名第三。 ###### 处理用户输入 - [ ] 始终验证最终可能显示给其他用户的用户输入。尝试将字符、字符串列入黑名单或清理输入通常是无效的([参见如何绕过此类黑名单的示例](https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet)))。白名单方法通常更安全。*缓解多种 XSS 攻击。* - [ ] 考虑使用 [loofah-activerecord](https://github.com/flavorjones/loofah-activerecord) gem 清理您的模型属性值。*缓解多种 XSS 攻击*。 - [ ] 如果必须根据用户输入的 URL 创建链接,请务必对其进行验证。特别是,在几乎所有情况下都应该可以将 URL scheme 限制为 http/https。传递给 `link_to`(第二个参数)的 URL 将进行 HTML 转义。但是,`link_to` 允许 URL 使用任何 scheme。如果使用正则表达式,请确保字符串**开头**为预期的协议,如 `\Ahttps?`。*缓解 XSS 攻击,例如输入 `javascript:dangerous_stuff()//http://www.some-legit-url.com` 作为网站 URL,或显示给其他用户的危险data:` payload(例如在用户个人资料页中)。* - [ ] 使用正则表达式进行输入验证时,请使用 `\A` 和 `\z` 匹配字符串的开头和结尾。**不要**使用 `^` 和 `$` 作为锚点。*缓解涉及在换行符后插入 JS 代码的 XSS 攻击,例如 `me@example.com\n`。* - [ ] 不要信任在客户端(前端)实施的验证,因为大多数实现都可以被绕过。务必在服务器端(重新)验证。 ###### 输出转义与清理 - [ ] 转义所有 HTML 输出。Rails 默认执行此操作,但在视图中调用 `html_safe` 或 `raw` 会抑制转义。查找整个项目中对这些方法的调用,检查您是否正在从用户输入的字符串生成 HTML,以及这些字符串是否已得到有效验证。请注意,有[几十种方法可以绕过验证](https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet))。如果可能,请完全避免调用 `html_safe` 和 `raw`。大多数模板库也提供一种跳过转义的方法。ERB 使用双等号:`<%== params[:query] %>`。对于自定义清理,请参阅 [ActionView::Helpers::SanitizeHelper](http://api.rubyonrails.org/classes/ActionView/Helpers/SanitizeHelper.html)。*缓解 XSS 攻击。* - [ ] 始终用双引号将属性值括起来。即使没有 `html_safe`,也有可能通过未加引号的属性将跨站脚本引入模板。在以下代码 `

...

` 中,攻击者可以在 style 参数中插入一个空格,payload 突然脱离了属性值,他们可以插入自己的 payload。当受害者将鼠标悬停在该段落上时,XSS payload 就会触发。*缓解 XSS 攻击。* - [ ] 在 HTML 模板中渲染 JSON 很棘手。您不能简单地对 JSON 进行 HTML 转义,尤其是在将其插入 script 上下文时,因为双引号会被转义从而破坏代码。但不转义它也是不安全的,因为浏览器无论在哪里都会将 `` 标签视为 HTML。Rails 文档建议始终使用 `json_escape`,以防 `to_json` 被覆盖或该值不是有效的 JSON。*缓解 XSS 攻击。* - [ ] 使用 `render inline: ...` 时要小心。传入的值默认将被视为 ERB 模板。看看这段代码:`render inline: "Thanks #{@user.name}!"`。假设用户可以设置自己的名字,攻击者可能会将他们的名字设置为 `<%= rm -rf / %>`,这将执行 `rm -rf /` 在服务器上!这被称为服务器端模板注入,它允许在服务器上执行任意代码 (RCE)。如果必须使用内联模板,请像在常规 ERB 模板中那样处理所有输入:`render inline: "Thanks <%= @user.name %>"`。*缓解 XSS 攻击。* - [ ] 避免在发送给其他用户的电子邮件中包含用户输入的字符串。攻击者可能会在预期不包含 URL 且不提供 URL 验证的自由文本字段中输入恶意 URL。大多数电子邮件客户端会将 URL 显示为链接。*缓解 XSS、网络钓鱼、恶意软件感染和其他攻击。* - [ ] 如果 I18n 键以 `_html` 结尾,它将自动被标记为 html safe,而键插值将被转义!请参阅 [(示例代码)](#when-i18n-key-ends-up-with-_html)。 ###### HAML 模板中的 XSS 防护 - [ ] 在 Haml 中使用 `!=` 时要小心,应确保没有未转义的用户数据被渲染。Haml 中的 `!=` 表示法的工作方式与 ERB 中的 `<%= raw(…) %>` 相同。请参阅 [(示例代码)](#haml-xss-protection)。 资源: - [Ruby on Rails 安全指南 - XSS](http://guides.rubyonrails.org/security.html#cross-site-scripting-xss) - [OWASP XSS 过滤器规避备忘单](https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet) - [OWASP Ruby on Rails 备忘单 - 跨站脚本 (XSS)](https://www.owasp.org/index.php/Ruby_on_Rails_Cheatsheet#Cross-site_Scripting_.28XSS.29) - [Plataformatec 博客 - Rails 4.2 中新的 HTML 清理器](http://blog.plataformatec.com.br/2014/07/the-new-html-sanitizer-in-rails-4-2) - [Brakeman Pro - Rails 中的跨站脚本](https://brakemanpro.com/2017/09/08/cross-site-scripting-in-rails) - [预防 Rails 中的安全问题](https://www.railscarma.com/blog/technical-articles/preventing-security-issues-rails/) - [Rails 应用的安全提示](https://drivy.engineering/security-tips-for-rails-apps/) ###### 内容安全策略 (CSP) - [ ] 内容安全策略 (CSP) 是一个额外的安全层,有助于检测和缓解对我们 Web 应用程序的多种攻击,包括跨站脚本 (XSS) 和数据注入攻击。 资源: - [配置内容安全策略的 Rails 5.2 DSL](https://blog.bigbinary.com/2018/10/23/rails-5-2-adds-dsl-for-configuring-content-security-policy-header.html) #### 不安全的直接对象引用 - [ ] 当用户本应有权访问 url(例如 `"/get/post/6"`)而无权访问 `"/get/post/9"`,但系统未正确检查这些权限时,就会出现 IDOR 问题。如果我们更改 URL 中的 "6",会发生什么?我们可以看到所有用户的数据。这可能是因为数据是按如下方式生成的:`@user = User.find_by(id: params[:user_id])` ——这基本上是从 URL 的 GET 参数中获取 ID。相反,更安全的做法是基于 `"current_user"` 会话变量设置 `@user` 参数,如下所示:`@user = current_user`。 资源: - [Rails 漏洞及在哪里找到它们 – 第 1 部分](https://www.vdalabs.com/2018/01/12/rails-vulnerabilities-find-part-1/) #### HTTP 与 TLS - [ ] 强制 HTTPS 优先于 TLS(以前称为 SSL)。在 `config/environments/production.rb` 中设置 `config.force_ssl = true`。也可以在 TLS 终端点(如负载均衡器、Nginx 或 Passenger Standalone)中执行此操作。*缓解中间人和其他攻击。* - [ ] 使用 [Qualys SSL Lab 的 SSL 服务器测试工具](https://www.ssllabs.com/ssltest/) 检查您的 TLS 证书等级。确保使用最强(且广泛兼容)的协议和密码套件,最好是支持短暂 Diffie-Hellman 的套件。[Mozilla SSL 配置生成器](https://mozilla.github.io/server-side-tls/ssl-config-generator/) 可以为您提供一些建议。*缓解多种与 SSL/TLS 相关的攻击,例如 BEAST 和 POODLE。* - [ ] 考虑限制传入的 HTTP 请求速率,如 [rack-attack](https://github.com/kickstarter/rack-attack) 和 [rack-throttle](https://github.com/dryruby/rack-throttle) gem 所实现的那样。请参阅[示例代码](#throttling-requests)。*缓解 Web 爬取、HTTP 泛洪和其他攻击。* ###### 安全相关的请求头 - [ ] 考虑使用 [Secure Headers gem](https://github.com/twitter/secureheaders)。*缓解多种攻击。* - [ ] 考虑混淆 Web 服务器横幅字符串。换句话说,隐藏您的 Web 服务器名称和版本。*缓解 HTTP 指纹识别,使攻击者更难确定哪些漏洞可能对您的 Web 服务器有效。* #### Memcached 安全 - [ ] 使用防火墙。Memcached 需要能被您的其他服务器访问,但没有理由将其暴露在互联网上。简而言之,只有您的其他生产服务器才能访问您的生产 memcached 服务器。仅此一项就可以防止您的服务器被用于攻击。Memcached 开箱即不使用身份验证,因此任何可以连接到您的服务器的人都能够读取您的数据。 - [ ] 监听私有接口。如果您为 Rails 应用程序和 memcached 运行一台服务器,则应监听 `127.0.0.1`。出于可用性考虑,无论如何您都不应该在生产环境中只有 1 台服务器。对于预发布和测试环境,请遵循此规则。对于您有多个需要连接到 memcached 的 Rails 服务器的生产设置,请使用服务器的私有 IP。类似于 `192.168.0.1`、`172.16.0.1` 或 `10.0.0.1`。当您启动 memcached 时,请使用 `--listen 127.0.0.1` 或 `--listen 192.168.0.1`。 - [ ] 禁用 UDP。它默认是启用的。要禁用 UDP,请在启动 memcached 时使用 `-U 0`。 资源: - [Memcached 安全,又名不要攻击 GitHub](https://www.engineyard.com/blog/memcached-security-aka-dont-attack-github-) #### 授权 (Pundit) - [ ] 在后端实施授权。仅在 UI 中隐藏链接/控件不足以保护资源免受未经授权的访问。*缓解强制浏览攻击。* - [ ] 确保所有需要授权的控制器/操作都调用 `authorize` 或 `policy_scope` 方法([示例代码](#pundit-ensure-all-actions-are-authorized))。*缓解因开发人员忘记在某些控制器操作中要求授权而导致的强制浏览攻击。* - [ ] 当使用与用户关联的数据库记录来填充选择框、单选按钮或复选框时,请考虑使用 `policy_scope` 而不是按关联查询 (`user.posts`)。请参阅[更多详细信息和示例代码](#pundit-ensure-all-actions-are-authorized)。*提高授权策略的可读性和可维护性。* 资源: - [Pundit:确保使用了策略和作用域](https://github.com/elabs/pundit#ensuring-policies-and-scopes-are-used) - [Pundit:作用域](https://github.com/elabs/pundit#scopes) #### 文件 ###### 文件上传 - [ ] 避免使用用户控制的文件名。如果可能,在将上传的文件存储在操作系统中时为其分配“随机”名称。如果不可能,请将可接受的字符列入白名单。拒绝文件名中包含无效字符的上传比尝试清理它们更安全。*缓解目录遍历攻击,例如试图通过上传名为 `../../passwd` 的文件来覆盖系统文件。* - [ ] 避免在您的服务器上使用诸如 ImageMagick 之类的库来处理图像和视频。如果可能,请使用图像/视频处理服务,例如 [Transloadit](https://transloadit.com/)、[Cloudinary](http://cloudinary.com/features#manipulation) 或 [imgix](https://www.imgix.com/solutions)。*缓解多种与图像/视频处理相关的漏洞,例如[这些](https://imagetragick.com)。* - [ ] 如果使用 [paperclip](https://github.com/thoughtbot/paperclip) gem 和 [imagemagick](https://www.imagemagick.org) 进行文件上传和处理,请确保: - Imagemagick [策略](https://www.imagemagick.org/source/policy.xml) 适合您的环境,以避免诸如 [像素洪水攻击]( https://hackerone.com/reports/390) 之类的漏洞利用。 - 内容欺骗是手动处理的,因为它在诸如 [#2426](https://github.com/thoughtbot/paperclip/issues/2426) 之类的场景中会失败。 - [ ] 异步处理上传的文件。如果不可能,请实施按客户端的速率限制。*缓解涉及通过大量需要处理的上传来使服务器 CPU 过载的 DoS 攻击。* - [ ] 不要信任在客户端(前端)实施的验证,因为大多数实现都可以被绕过。务必在服务器端(重新)验证。 - [ ] 在处理之前验证文件。*缓解诸如图像炸弹之类的 DoS 攻击。* - [ ] 将可接受的文件扩展名和可接受的媒体类型(以前称为 MIME 类型)列入白名单。仅验证文件扩展名而不检查其媒体类型是不够的,因为攻击者可能通过更改扩展名来伪装恶意文件。*缓解上传危险文件格式(如 shell 或 Ruby 脚本)。* - [ ] 限制文件大小。*缓解涉及上传非常大文件的 DoS 攻击。* - [ ] 考虑直接从客户端(浏览器)上传到 S3 或类似的云存储服务。*通过将上传的文件保存在与您的 Rails 应用程序不同的服务器上来缓解多种安全问题。* - [ ] 如果允许上传易受恶意软件感染的文件(例如 exe、msi、zip、rar、pdf),请扫描病毒/恶意软件。如果可能,请使用第三方服务在您的服务器之外进行扫描。*缓解服务器感染(主要在 Windows 服务器中)并向其他用户提供受感染的文件。* - [ ] 如果允许上传诸如 zip、rar 和 gz 之类的档案,请在解压**之前**验证目标路径、估计的解压大小和压缩文件的媒体类型。*缓解诸如 zip 炸弹等 DoS 攻击、将恶意文件打包以试图绕过验证,以及覆盖诸如 `/etc/passwd` 之类的系统文件。* ###### 文件下载 - [ ] 不允许下载用户提交的文件名和路径。如果不可能,请使用允许的文件名和路径白名单。*缓解利用目录遍历漏洞下载敏感文件。* 资源: - [Ruby on Rails 安全指南 - 文件上传和下载](http://guides.rubyonrails.org/security.html#file-uploads) #### 跨站请求伪造 (CSRF) - [ ] 通过在 Web 视图使用的或 `ApplicationController` 中的所有控制器中设置 `protect_from_forgery with: :exception` 来强制执行 CSRF 保护。 - [ ] 以 RESTful 方式使用 HTTP 动词。不要使用 GET 请求来更改资源状态。*缓解 CSRF 攻击。* - [ ] 直到 Rails 4,所有表单、操作和方法都只有一个 CSRF 令牌。Rails 5 实现了按表单的 CSRF 令牌,这些令牌仅对单个表单和操作/方法有效。通过设置 `config.action_controller.per_form_csrf = true` 来启用它。 资源: - [Ruby on Rails 安全指南 - CSRF](http://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf) - [Big Binary 博客 - Rails 5 中的每个表单都有自己的 CSRF 令牌](http://blog.bigbinary.com/2016/01/11/per-form-csrf-token-in-rails-5.html) #### 跨域资源共享 (CORS) - [ ] 偶尔会出现需要跨多个域共享某些资源的情况。例如,您希望使用 AJAX 请求上传文件并将其发送到另一个应用程序。接收方应指定允许发出这些请求的域白名单。有几个 HTTP 请求头控制着这一点。您可以使用 `rack-cors` gem 并在 `config/application.rb` 中指定您的配置([代码示例](#rack-cors-configuration))。 资源: - [RoR 中的安全问题解决方案](https://syndicode.com/2017/10/23/security-issues-solutions-in-ror/) #### 敏感数据泄露 - [ ] 如果可能,避免在您的应用程序中存储敏感数据,例如信用卡、税号和第三方身份验证凭证。如果不可能,请确保所有静态(在数据库中)和传输中的敏感数据都已加密(使用基于 TLS 的 HTTPS)。*缓解敏感数据的盗窃/泄露。* - [ ] 不要记录诸如密码和信用卡号之类的敏感数据。您可以在 `initializers/filter_parameter_logging.rb` 中的 `config.filter_parameters` 中包含包含敏感数据的参数。为了增加安全性,请考虑将 `filter_parameters` 转换为白名单。请参阅[示例代码](#convert-filter_parameters-into-a-whitelist)。*防止在日志文件中以明文形式存储敏感数据。* - [ ] HTML 注释对客户端是可见的,不应包含可能对攻击者有用的详细信息。考虑使用服务器端注释,例如 `<%# This comment syntax with ERB %>` 而不是 HTML 注释。*避免暴露实现细节。* - [ ] 避免在 URL、表单 HTML 源代码和 API 中暴露数字/顺序记录 ID。考虑使用 slug(又名友好 ID、自定义 URL)而不是数字 ID 来标识记录,如 [friendly_id gem](https://github.com/norman/friendly_id) 所实现的那样。额外的好处包括 SEO 和更好看的 URL。*缓解强制浏览攻击和暴露有关您业务的指标,例如注册用户数量、库存产品数量或收据/购买数量。* - [ ] 如果 URL 使用 slug 而不是数字 ID,请考虑在发生授权错误时返回 `404 Not Found` 状态码而不是 `403 Forbidden`。防止泄露用于生成 slug 的属性值。例如,访问 `www.myapp.com/users/john-doe` 并获得 `403` 返回状态表明该应用程序有一个名为 John Doe 的用户。* - [ ] 不要在生产环境中设置 `config.consider_all_requests_local = true`。如果您需要设置 `config.consider_all_requests_local = true` 才能使用 [better_errors](https://github.com/charliesome/better_errors) gem,请在 `config/environments/development.rb` 中进行。*防止泄露仅开发人员能访问的异常和其他信息。* - [ ] 不要在生产环境中安装与开发/测试相关的 gem,例如 [better_errors](https://github.com/charliesome/better_errors) 和 [web-console](https://github.com/rails/web-console)。将它们放在 `Gemfile` 中的 `group :development, :test do` 块中。*防止泄露异常,甚至如果使用 better_errors + web-console 会提供 **REPL 访问权限**。* ###### 凭证 - [ ] 位于 `config/master.key` 上的加密密钥是在您运行 `rails new` 时创建的。它也被添加到 `.gitignore`,因此不会被提交到您的代码库。*缓解凭证泄露/被盗。* - [ ] 不要直接编辑 `config/credentials.yml.enc` 文件。要添加凭证,请运行 `bin/rails credentials:edit`。使用扁平格式,这意味着您不再需要放置开发或生产环境标记。*缓解凭证泄露/被盗。* - [ ] 如果您想生成一个新的 secret key base 运行,`bin/rails secret` 并通过运行 `bin/rails credentials:edit` 将其添加到您的凭证中。 - [ ] 安全地上传 `master.key`。您可以通过 scp 或 sftp 传输该文件。将密钥上传到共享目录。这里的共享是指版本之间的共享,而不是共享文件系统。在每次部署时,您将 `config/master.key` 符号链接到 `/path/to/shared/config/master.key`。 - [ ] 如果您需要将密钥副本提供给开发人员,切勿通过电子邮件发送(除非您使用的是加密电子邮件,而我们大多数人都不使用!)。您可以使用密码管理器,因为它们使用加密。 - [ ] 将密钥放在 `RAILS_MASTER_KEY` 环境变量中。在某些无法上传文件的情况下,这是唯一的选择。尽管这很方便,但请确保您了解使用环境变量的风险。风险是可以缓解的,但如果您可以上传 master.key,请使用该选项。 资源: - [Rails 5.2 上的 Rails 加密凭证](https://www.engineyard.com/blog/rails-encrypted-credentials-on-rails-5.2) #### 路由、模板选择与重定向 - [ ] 不要根据用户输入的字符串执行 URL 重定向。换句话说,不要将用户输入传递给 `redirect_to`。如果您别无选择,请创建一个允许的重定向 URL 白名单,或者限制仅重定向到您域内的路径 [(示例代码)](https://www.owasp.org/index.php/Ruby_on_Rails_Cheatsheet#Redirects_and_Forwards)。*缓解重定向到网络钓鱼和恶意软件站点。防止攻击者向受害者提供诸如 `http://www.my-legit-rails-app.com/redirect?to=www.dangeroussite.com` 之类的 URL。* - [ ] 不要使用用户输入的字符串来确定要渲染的模板或视图的名称。*防止攻击者渲染任意视图,例如仅限管理员的页面。* - [ ] 避免使用“包罗万象”的路由,例如 `match ':controller(/:action(/:id(.:format)))'`,并将非 action 的控制器方法设为 private。*缓解对控制器方法的意外访问。* 资源: - [OWASP Ruby on Rails 备忘单 - 重定向和转发 (URL 验证)](https://www.owasp.org/index.php/Ruby_on_Rails_Cheatsheet#Redirects_and_Forwards) #### 第三方软件 - [ ] 经常在操作系统中应用最新的安全补丁。特别注意面向互联网的服务,例如应用程序服务器、Web 服务器 和 SSH 服务器。 - [ ] 经常更新 Ruby。 - [ ] 注意您的 gem 中的安全漏洞。经常运行 [bundler-audit](https://github.com/rubysec/bundler-audit) 或使用诸如 [Snyk](https://snyk.io)、[GuardRails](https://www.guardrails.io/) 之类的服务(两者对于开源开发均免费)。 #### 安全工具 - [ ] 在每次部署之前运行 [Brakeman](http://brakemanscanner.org/)。如果使用诸如 [Code Climate](https://codeclimate.com/) 的自动代码审查工具,请启用 [Brakeman 引擎](https://docs.codeclimate.com/v1.0/docs/brakeman)。 - [ ] 使用 `MediumSecurity` 添加 gem 信任策略是阻止在服务器上安装恶意 gem 的好方法。例如,`bundle --trust-policy MediumSecurity`。 - [ ] 您可以使用 `rubocop` gem 并在 `.rubocop.yml` 配置文件中启用安全相关规则。 - [ ] 考虑使用持续安全服务,例如 [Detectify](https://detectify.com/)。 - [ ] 考虑使用 Web 应用程序防火墙 (WAF),例如用于 Nginx 的 [NAXSI](https://github.com/nbs-system/naxsi)、用于 Apache 和 Nginx 的 [ModSecurity](https://github.com/SpiderLabs/ModSecurity)。*缓解 XSS、SQL 注入、DoS 和许多其他攻击。* 资源: - [保护 Rails 应用程序的安全](https://fq.nz/blog/2019/04/14/keeping-rails-application-secured.html) - [RuboCop 如何保护您的 Ruby 和 Rails 应用程序](https://www.guardrails.io/blog/2018/11/25/how-rubocop-can-secure-your-ruby-and-rails-applications) #### 测试 - [ ] 在您的测试套件中包含安全测试。查看 OWASP 的 [RailsGoat](https://github.com/OWASP/railsgoat) 应用程序以获取[与安全相关的 Capybara specs](https://github.com/OWASP/railsgoat/tree/master/spec/vulnerabilities) 示例。*提高额外的安全意识并缓解与安全相关的回归。* - [ ] 成对创建安全测试:一个用于访问被拒绝场景,另一个用于访问被允许场景。 - [ ] 使用 TDD 时,请考虑在开发的早期阶段实施身份验证,因为它往往会破坏多个预先存在的测试。 #### 其他 - [ ] 在控制器中使用 strong parameters。从 Rails 4+ 起,这是默认行为。*缓解批量赋值攻击,例如为了提升权限而覆盖 `User` 模型的 `role` 属性。* - [ ] 在公开暴露的表单上实施验证码或负向验证码。[reCAPTCHA](https://developers.google.com/recaptcha/) 是一个很好的选择,并且有一个 [gem](https://github.com/ambethia/recaptcha) 可以促进 Rails 集成。其他选项包括 [rucaptcha](https://github.com/huacnlee/rucaptcha) 和 [negative-captcha](https://github.com/subwindow/negative-captcha) gem。*缓解自动垃圾邮件 (spambots)。* ## 详细信息和代码示例 #### 命令注入示例 ``` # User input params[:shop][:items_ids] # Maybe you expect this to be an array inside a string. # But it can contain something very dangerous like: # "Kernel.exec('Whatever OS command you want')" # 漏洞代码 evil_string = params[:shop][:items_ids] eval(evil_string) ``` 如果您看到对 eval 的调用,您必须非常确定您已对其进行了正确的清理。使用正则表达式是实现此目的的好方法。 ``` # 安全代码 evil_string = params[:shop][:items_ids] secure_string = /\[\d*,?\d*,?\d*\]/.match(evil_string).to_s eval(secure_string) ``` #### 密码验证正则表达式 我们可以通过将以下代码添加到 `User` 模型中,在 Devise 中实现密码强度验证。 ``` validate :password_strength private def password_strength minimum_length = 8 # Regex matches at least one lower case letter, one uppercase, and one digit complexity_regex = /\A(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])/ # When a user is updated but not its password, the password param is nil if password.present? && (password.length < minimum_length || !password.match(complexity_regex)) errors.add :password, 'must be 8 or more characters long, including at least one lowercase letter, one uppercase letter, and one digit.' end end ``` #### Pundit:确保所有操作均已授权 将以下内容添加到 `app/controllers/application_controller.rb` ``` after_action :verify_authorized, except: :index, unless: :devise_controller? after_action :verify_policy_scoped, only: :index, unless: :devise_controller? ``` 将以下内容添加到不需要授权的控制器。出于 DRY 目的,您可以创建一个 concern。 ``` after_action_skip :verify_authorized after_action_skip :verify_policy_scoped ``` #### Pundit:在选择框中仅显示适当的记录 设想一个类似博客的新闻网站,其中具有 `editor` 角色的用户可以访问特定的新闻类别,而 `admin` 用户可以访问所有类别。`User` 和 `Category` 模型具有 HMT 关系。在创建博客文章时,会有一个用于选择类别的选择框。我们希望编辑者只能在选择框中看到与其关联的类别,而管理员必须看到所有类别。我们可以使用 `user.categories` 填充该选择框。但是,我们将不得不将所有管理员用户与所有类别相关联(并在每次创建新类别时更新这些关联)。更好的方法是使用 [Pundit Scopes](https://github.com/elabs/pundit#scopes) 来确定哪些类别对每个用户角色可见,并在填充选择框时使用 `policy_scope` 方法。 ``` # app/views/posts/_form.html.erb f.collection_select :category_id, policy_scope(Category), :id, :name ``` #### 将 filter_parameters 转换为白名单 开发人员可能会忘记将一个或多个包含敏感数据的参数添加到 `filter_parameters`。白名单通常比黑名单更安全,因为在开发人员遗忘的情况下它们不会产生安全漏洞。以下代码将 `filter_parameters` 转换为白名单。 ``` # config/initializers/filter_parameter_logging.rb if Rails.env.production? # Parameters whose values are allowed to appear in the production logs: WHITELISTED_KEYS = %w(foo bar baz) # (^|_)ids? matches the following parameter names: id, *_id, *_ids WHITELISTED_KEYS_MATCHER = /((^|_)ids?|#{WHITELISTED_KEYS.join('|')})/.freeze SANITIZED_VALUE = '[FILTERED]'.freeze Rails.application.config.filter_parameters << lambda do |key, value| unless key.match(WHITELISTED_KEYS_MATCHER) value.replace(SANITIZED_VALUE) end end else # Keep the default blacklist approach in the development environment Rails.application.config.filter_parameters += [:password] end ``` #### rack-cors 配置 ``` module Sample class Application < Rails::Application config.middleware.use Rack::Cors do allow do origins 'someserver.example.com' resource %r{/users/\d+.json}, headers: ['Origin', 'Accept', 'Content-Type'], methods: [:post, :get] end end end end ``` #### 请求限流 在某些页面(如登录页面)上,您会希望将用户限制为每分钟几次请求。这可以防止机器人快速尝试数千个密码。 Rack Attack 是一个 Rack 中间件,它提供了限流和其他功能。 ``` Rack::Attack.throttle('logins/email', :limit => 6, :period => 60.seconds) do |req| req.params['email'] if req.path == '/login' && req.post? end ``` #### 当 I18n 键以 \_html 结尾时 代替以下示例: ``` # en.yml en: hello: "Welcome %{user_name}!" ``` ``` <%= t('hello', user_name: current_user.first_name).html_safe %> ``` 使用以下内容: ``` # en.yml en: hello_html: "Welcome %{user_name}!" ``` ``` <%= t('hello_html', user_name: current_user.first_name) %> ``` #### HAML:XSS 防护 默认情况下, ``` ="emphasized" != "emphasized" ``` 编译为: ``` <em>emphasized</em> emphasized ``` ## 作者 - **Bruno Facca** - [LinkedIn](https://www.linkedin.com/in/brunofacca/) - 电子邮件:bruno at facca dot info ## 贡献 欢迎贡献。如果您想更正错误或向检查清单中添加新项目,请随时创建 issue,然后提交 PR。有关贡献建议,请参阅 [TODO](#TODO) 部分。 如果您有兴趣定期贡献,请通过上述电子邮件与我联系以成为合作者。 ## TODO * 添加示例测试(RSpec 和/或 Minitest)以检测漏洞的存在。请参阅 OWASP 的 [RailsGoat 与安全相关的 Capybara specs](https://github.com/OWASP/railsgoat/tree/master/spec/vulnerabilities) 以获取灵感。 * 比较上传 gem 关于它们对本检查清单 [文件上传](#file-uploads) 项目的实现(建立一个表格)。 * 比较身份验证 gem 关于它们对本检查清单 [身份验证](#authentication) 项目的实现(建立一个表格)。 ## 参考资料与延伸阅读 - [Ruby on Rails 安全指南](http://guides.rubyonrails.org/security.html) - [Obie Fernandez 的 The Rails 4 Way](https://www.amazon.com/Rails-Way-Addison-Wesley-Professional-Ruby/dp/0321944275/),第 15 章 - [OWASP Top Ten](https://www.owasp.org/index.php/Top_10_2013-Top_10) - [SitePoint:常见的 Rails 安全陷阱及其解决方案](https://www.sitepoint.com/common-rails-security-pitfalls-and-their-solutions/) - [Hardhat 的 Rails 安全审计](https://github.com/hardhatdigital/rails-security-audit) - [Eliot Sykes 的 Rails 安全检查清单](https://github.com/eliotsykes/rails-security-checklist) - [Ruby on Rails 安全 17 项检查清单](https://www.engineyard.com/blog/ruby-on-rails-security-checklist) - [Rails 安全最佳实践](https://github.com/ankane/secure_rails) - [Awesome Ruby 安全资源](https://github.com/pxlpnk/awesome-ruby-security) - [Awesome Rails 安全](https://github.com/edwardqiu/awesome-rails-security) - [保护 Rails 中的敏感数据](https://ankane.org/sensitive-data-rails) ## 许可证 在 [MIT 许可证](https://opensource.org/licenses/MIT) 下发布。
标签:CISA项目, Cookies安全, HAML, Rails 4, Rails 5, Ruby on Rails, Web安全, XML 请求, 会话管理, 内存转储, 内容安全策略 (CSP), 安全开发, 安全清单, 安全编码, 安全防护指南, 应用程序安全, 开发者指南, 开源安全项目, 注入攻击, 漏洞预防, 蓝队分析, 跨站脚本攻击 (XSS), 输入校验, 输出转义