ruby-on-rails - csrf rails




了解Rails真实性令牌 (7)

我遇到了一些关于Rails中真实性令牌的问题,因为我现在已经有很多次了。

但我真的不想解决这个问题,继续下去。 我真的很想了解真实性标记。 那么,我的问题是,你有这方面的一些完整的信息来源,或者你会花时间在这里详细解释吗?


什么是CSRF?

真实性令牌是跨站点请求伪造(CSRF)的一种对策。 你问什么是CSRF?

这是一种攻击者可能会在不知道会话令牌的情况下劫持会话的方式。

场景

  • 访问您的银行网站,登录。
  • 然后访问攻击者的网站(例如,来自不受信任的组织的赞助广告)。
  • 攻击者的页面包含与银行“转账资金”表单相同字段的表单。
  • 攻击者知道您的账户信息,并且已经预先填写了表单域,可以将账户中的资金转移到攻击者的账户。
  • 攻击者的页面包括向您的银行提交表单的Javascript。
  • 表单提交后,浏览器会包含您的银行网站Cookie,包括会话令牌。
  • 银行将资金转移到攻击者的账户。
  • 该表单可以位于不可见的iframe中,因此您永远不会知道发生了攻击。
  • 这被称为跨站点请求伪造(CSRF)。

CSRF解决方案

  • 服务器可以标记来自服务器本身的表单
  • 每个表单必须包含一个额外的身份验证令牌作为隐藏字段。
  • 令牌必须是不可预知的(攻击者无法猜测它)。
  • 服务器在页面的表单中提供有效的令牌。
  • 服务器在发布表单时检查标记,拒绝没有正确标记的表单。
  • 令牌示例:使用服务器密钥加密的会话标识符。
  • Rails自动生成这样的标记:请参阅每种形式的authenticity_token输入字段。

怎么了

当用户查看表单以创建,更新或销毁资源时,Rails应用程序会创建一个随机authenticity_token ,将该令牌存储在会话中,并将其放置在表单中的隐藏字段中。 当用户提交表单时,Rails会查找authenticity_token ,并将其与存储在会话中的值进行比较,如果匹配,则允许继续此请求。

为什么会发生

由于真实性标记存储在会话中,客户端无法知道其值。 这可以防止人们在未查看该应用程序本身内的表单的情况下将表单提交给Rails应用程序。 想象一下,你正在使用服务A,你登录到服务,一切都很好。 现在想象一下,您使用了服务B,并且您看到了一张您喜欢的图片,并按下了图片以查看它的更大尺寸。 现在,如果服务B中存在一些恶意代码,它可能会向服务A(您登录的服务)发送请求,并通过向http://serviceA.com/close_account发送请求来要求删除您的帐户。 这就是所谓的CSRF(跨站点请求伪造)

如果服务A使用真实性令牌,则此攻击向量不再适用,因为来自服务B的请求不包含正确的真实性令牌,并且将不被允许继续。

API文档描述了有关元标记的详细信息:

使用protect_from_forgery方法打开CSRF保护,该方法检查令牌,并在会话不符合预期时重置该会话。 默认情况下会为新的Rails应用程序生成对此方法的调用。 token参数默认命名为authenticity_token 。 通过在HTML头中包含csrf_meta_tags ,该标记的名称和值必须添加到呈现表单的每个布局中。

笔记

请记住,Rails只验证不是幂等方法(POST,PUT / PATCH和DELETE)。 GET请求不被检查真实性标记。 为什么? 因为HTTP规范声明GET请求是幂等的,不应该在服务器上创建,更改或销毁资源,并且请求应该是幂等的(如果多次运行相同的命令,则每次都应该得到相同的结果)。

同样,真正的实现在开始时定义更复杂一些,以确保更好的安全性。 Rails不会为每个表单发布相同的存储令牌。 它也不会每次生成和存储不同的标记。 它会在会话中生成并存储加密散列,并在每次呈现页面时发布新的加密令牌,这些令牌可以与存储的令牌相匹配。 请参阅request_forgery_protection.rb

教训

使用authenticity_token保护您的非幂等方法(POST,PUT / PATCH和DELETE)。 另外请确保不允许任何可能会修改服务器资源的GET请求。

编辑:检查@erturne关于GET请求是幂等的评论 。 他用比我在这里做的更好的方式解释它。


什么是authentication_token?

这是一个由Rails应用程序使用的随机字符串,用于确保用户正在从应用页面请求或执行操作,而不是从其他应用或站点进行操作。

为什么需要authentication_token?

保护您的应用程序或网站免遭跨站请求伪造。

如何将一个authentication_token添加到表单中?

如果使用form_for标记生成表单,则会自动添加authentication_token,否则可以使用<%= csrf_meta_tag %>


可以防止的最小攻击示例

在我的网站evil.com我说服你提交以下表格:

<form action="http://bank.com/transfer" method="post">
  <p><input type="hidden" name="to"      value="ciro"></p>
  <p><input type="hidden" name="ammount" value="100"></p>
  <p><button type="submit">CLICK TO GET PRIZE!!!</button></p>
</form>

如果您通过会话cookie登录到您的银行,那么cookies将被发送,并且转账将在您不知情的情况下完成。

这就是CSRF令牌的作用:

  • 用返回表单的GET响应,Rails发送一个非常长的随机隐藏参数
  • 当浏览器发出POST请求时,它将一起发送参数,并且服务器将只接受它,如果它匹配

所以真正的浏览器上的表单看起来像这样:

<form action="http://bank.com/transfer" method="post">
  <p><input type="hidden" name="authenticity_token" value="j/DcoJ2VZvr7vdf8CHKsvjdlDbmiizaOb5B8DMALg6s=" ></p>
  <p><input type="hidden" name="to"                 value="ciro"></p>
  <p><input type="hidden" name="ammount"            value="100"></p>
  <p><button type="submit">Send 100$ to Ciro.</button></p>
</form>

因此,我的攻击会失败,因为它没有发送authenticity_token参数,而且我也不可能猜到它,因为它是一个巨大的随机数。

这种预防技术被称为同步器令牌模式

由于同源策略 ,同步器令牌模式工作:如果我可以从evil.com向您的银行发出XHR GET请求并读取结果,我将能够读取令牌并稍后再发出请求。 我已经进一步解释了这一点: https://security.stackexchange.com/a/72569/53321https://security.stackexchange.com/a/72569/53321

我强烈建议您阅读OWASP指南 ,了解这个以及其他任何安全问题。

Rails如何发送令牌

涵盖: Rails:csrf_meta_tag如何工作?

基本上:

  • 如果不是GET表单,HTML助手(如form_tag会为表单添加一个隐藏字段

  • AJAX由jquery-ujs自动处理, jquery-ujscsrf_meta_tags (存在于默认模板中)添加到您的标题中的meta元素中读取标记,并将其添加到所做的任何请求中。

    uJS还尝试更新过时缓存片段中的表单中的令牌。

其他预防方法


因为Authenticity Token非常重要,您可以使用Rails 3.0+

 <%= token_tag nil %>

创造

<input name="authenticity_token" type="hidden" value="token_value">

随地


如果您有来自同一客户端的多个并发请求,请注意真实性令牌机制可能导致争用条件。 在这种情况下,当服务器应该只有一个令牌时,您的服务器可以生成多个真实性令牌,并且由于会话Cookie令牌已被覆盖,接收到表单中较早令牌的客户端将在其下一个请求上失败。 有一个关于这个问题的文章,并不是一个完全无关紧要的解决方案: http://www.paulbutcher.com/2007/05/race-conditions-in-rails-sessions-and-how-to-fix-them/ : http://www.paulbutcher.com/2007/05/race-conditions-in-rails-sessions-and-how-to-fix-them/


真实性标记用于防止跨站请求伪造攻击(CSRF)。 要了解真实性标记,您必须先了解CSRF攻击。

CSRF

假设你是bank.com的作者。 您的网站上有一个表单,用于通过GET请求将资金转移到其他帐户:

黑客可以发送一个HTTP请求到服务器,说GET /transfer?amount=$1000000&account-to=999999 ,对不对?

错误。 黑客攻击行不通。 服务器基本上会想?

咦? 这家伙是谁试图启动转移。 这不是账户的拥有者,这是肯定的。

服务器如何知道这一点? 因为没有认证请求者的session_id cookie。

当您使用用户名和密码登录时,服务器会在浏览器上设置session_id cookie。 这样,您就不必使用用户名和密码验证每个请求。 当您的浏览器发送session_id cookie时,服务器知道:

哦,那是John Doe。 他在2.5分钟前成功登录。 他很好走。

黑客可能会认为:

嗯。 一个普通的HTTP请求将不起作用,但如果我能够接触到session_id cookie,那我会很有用。

用户浏览器为bank.com域设置了一堆cookie。 每当用户向bank.com域发出请求时,所有的cookies都会一起发送。 包括session_id cookie。

所以如果一个黑客可以让做出将钱转入他的账户的GET请求,他会成功的。 他怎么会欺骗你呢? 与跨站点请求伪造。

实际上,这很简单。 黑客可以让你访问他的网站。 在他的网站上,他可以有以下图片标签:

<img src="http://bank.com/transfer?amount=$1000000&account-to=999999">

当用户浏览器遇到该图像标记时,它将对该网址发出GET请求。 并且由于请求来自他的浏览器,所以它会发送与bank.com相关的所有cookie。 如果用户最近登录了bank.comsession_id cookie将被设置,并且服务器会认为用户想要将$ 1,000,000转帐到999999帐户!

那么,只是不要访问危险网站,你会没事的。

这还不够。 如果某人将该图像发布到Facebook并出现在您的墙上,该怎么办? 如果它被注入到您访问XSS攻击的网站会怎么样?

也不是那么坏。 只有GET请求易受攻击。

不对。 可以动态生成发送POST请求的表单。 下面是Rails安全指南中的例子:

<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>

真实性令牌

当你的ApplicationController有这个:

protect_from_forgery with: :exception

这个:

<%= form_tag do %>
  Form contents
<% end %>

被编译成这个:

<form accept-charset="UTF-8" action="/" method="post">
  <input name="utf8" type="hidden" value="&#x2713;" />
  <input name="authenticity_token" type="hidden" value="J7CBxfHalt49OSHp27hblqK20c9PgwJ108nDHX/8Cts=" />
  Form contents
</form>

特别是,会产生以下内容:

<input name="authenticity_token" type="hidden" value="J7CBxfHalt49OSHp27hblqK20c9PgwJ108nDHX/8Cts=" />

为了防止CSRF攻击,如果Rails没有看到与请求一起发送的真实性令牌,它将不会认为请求是安全的。

攻击者应该如何知道这个令牌是什么? 每次生成表单时随机生成一个不同的值:

跨站点脚本攻击(XSS)攻击 - 这就是如何。 但这是另一天不同的漏洞。





authenticity-token