许多现代的Web框架(如Laravel或Play框架)都具有内置支持,可保护您的Web应用程序免受跨站点请求伪造  (CSRF)的侵害  。这是一件好事,但是并不是每个开发人员何时以及如何使用它都总是不清楚。在本文中,我尝试给出一些(希望)易于理解的答案。

在潜入之前,让我们提醒自己一些规则和限制:

首先,在本文中,我假设您尚未通过设置CORS标头(  如)  将Web服务器配置为显式启用跨站点AJAX请求  X-Requested-With

  1. 如果满足此条件(这是默认设置),则Web浏览器将不向加载页面的域之外的域执行AJAX请求。 示例:从a.com加载的HTML页面无法通过b.com的AJAX请求和解析数据。
  2. HTML表单只能提交GET和POST请求,不能提交PUT,DELETE或任何其他请求。
  3. Cookie绑定到某个域(或子域),并且仅在Cookie域与加载页面的域匹配时才能设置和访问。 示例:从b.com加载的页面或其脚本无法读取为a.com设置的cookie。
  4. 您不能使用HTML表单提交自定义标题。唯一的例外是描述格式编码的标头(但这实际上不符合自定义标头的条件)。
  5. 您通过区分两种请求方法来坚持良好做法,这意味着两件事:
    (1)在服务器上,如果该表单旨在发出POST请求,则仅处理POST请求和POST数据。不要读取GET参数,并假设它们已被发布,例如在PHP中通过读取  $_REQUEST 而不是  $_POST。如果您期望POST请求并收到GET请求,则可以返回例如error error  405 Method Not Allowed
    (2)以一种仅将GET请求仅用于实际 从服务器获取数据的方式设计您的请求  ,而不对Web应用程序(例如数据库)的状态进行任何重大更改。例如,请勿使用GET请求让用户更改其电子邮件地址。如果只是一些日志记录,那没关系。

CSRF令牌有什么用处?

有效的CSRF令牌不会在服务器端告诉您客户端已发送有效或可信的数据,而是告诉您很有可能是用户向您发送数据的意图。永远记住:永远不要盲目信任客户端数据。您仍然需要验证数据。

如果这样做的话,难道用户并不总是打算发送数据吗?

不,我给你举个例子……

假设您在a.com上托管了一个类似简化Twitter的网站。登录的用户可以在表单中输入一些文本(推文),该文本作为POST请求发送到服务器,并在他们单击“提交”按钮时发布。在服务器上,通过包含其唯一会话ID的cookie来标识用户,因此您的服务器知道谁发布了Tweet。

表格可以很简单:

<form action="http://a.com/tweet" method="POST">
  <input type="text" name="tweet">
  <input type="submit">
</form>

现在想象一下,一个坏人将这个表格复制并粘贴到他的恶意网站上,比如说b.com。该表格仍然有效。只要用户登录到您的Twitter(即他们具有a.com的有效会话cookie),POST请求就会发送到http://a.com/tweet,并在用户正常时进行处理单击提交按钮。

到目前为止,只要让用户知道该表单的确切功能,这就不是什么大问题,但是如果我们的坏蛋像这样调整表单,该怎么办:

<form action="https://example.com/tweet" method="POST">
  <input type="hidden" name="tweet" value="Buy great products at http://b.com/#iambad">
  <input type="submit" value="Click to win!">
</form>

现在,如果您的用户之一最终进入了坏人的网站并单击“单击以赢取!”按钮,则表单已提交到您的网站,该用户可以通过Cookie中的会话ID正确识别,并且隐藏的Tweet发表。

如果我们的坏家伙变得更糟,他会让无辜的用户在使用JavaScript打开网页后立即提交此表单,甚至可能完全隐藏在一个看不见的iframe中。这基本上是跨站点请求伪造。

表单可以很容易地从任何地方提交到任何地方。通常,这是一个常见功能,但是在很多情况下,仅允许从其所属域提交表单很重要。

如果您的Web应用程序不区分POSTGET请求(例如,在PHP中使用$_REQUEST而不是$_POST),情况甚至会更糟。不要那样做!更改数据的请求可以像<img src="http://a.com/tweet?tweet=This+is+really+bad">嵌入恶意网站甚至电子邮件中一样容易地提交。

如何确保只能从自己的网站提交表格?

这就是CSRF令牌出现的地方。CSRF令牌是一个随机的,难以猜测的字符串。在具有您要保护的表单的页面上,服务器将生成一个随机字符串CSRF令牌,将其作为隐藏字段添加到表单中,并且还可以通过将其存储在会话中或通过设置cookie来记住它包含值。现在,表单将如下所示:

<form action="https://example.com/tweet" method="POST">
  <input type="hidden" name="csrf-token" value="nc98P987bcpncYhoadjoiydc9ajDlcn">
  <input type="text" name="tweet">
  <input type="submit">
</form>

当用户提交表单时,服务器只需将发布字段的值  csrf-token (名称无关紧要)与服务器记住的CSRF令牌进行比较。如果两个字符串相等,则服务器可以继续处理该表单。否则,服务器应立即停止处理表单并返回错误。

为什么这样做?

上面示例中的坏人无法获得CSRF令牌的原因有很多:

将静态源代码从我们的页面复制到其他网站是没有用的,因为隐藏字段的值随每个用户而变化。如果坏人的网站不知道当前用户的CSRF令牌,则服务器将始终拒绝该  POST 请求。

由于该恶意软件的恶意页面是由您的用户的浏览器从其他域(b.com而不是a.com)加载的,因此该恶意软件没有机会编写JavaScript的JavaScript,该JavaScript会加载内容,从而从用户的当前CSRF令牌加载你的网页。这是因为默认情况下,Web浏览器不允许跨域AJAX请求。

坏人也无法访问服务器设置的cookie,因为域不匹配。

什么时候应该防止跨站点请求伪造?

如果你能保证你不要混淆  GET,  POST 以及其他请求方法如上文所述,一个良好的开端将是保护所有  POST默认的请求。

您不必保护  PUT 和  DELETE 请求,因为如上所述,浏览器无法使用这些方法提交标准的HTML表单。

另一方面,JavaScript确实可以发出其他类型的请求,例如使用jQuery的$.ajax() 功能,但请记住,要使  AJAX请求正常工作,域必须匹配(只要您没有明确配置Web服务器即可)。

这意味着,即使您是POST 请求,通常甚至都不必向AJAX请求添加CSRF令牌  ,但是,如果POST 请求实际上是AJAX请求,则必须确保仅绕过Web应用程序中的CSRF检查。  。您可以通过查找X-Requested-WithAJAX请求通常包含的标头(如标头)  来实现。您还可以设置另一个自定义标头,并检查它在服务器端是否存在。这是安全的,因为浏览器不会将自定义标头添加到常规HTML表单提交中(请参见上文),因此Bad Guy先生没有机会使用表单来模拟这种行为。

如果您对AJAX请求有疑问,由于某种原因,您无法检查诸如的标头  X-Requested-With,只需将生成的CSRF令牌传递到JavaScript并将令牌添加到AJAX请求。有几种方法可以做到这一点。要么像常规HTML表单一样将其添加到有效负载中,要么将自定义标头添加到AJAX请求中。只要您的服务器知道要在传入请求中查找的位置,并且能够将其与从会话或cookie中记住的原始值进行比较,就可以对您进行排序。

via https://cloudunder.io/blog/csrf-token

更多请参考 : https://docs.djangoproject.com/en/3.0/ref/csrf/

什么是CSRF令牌,它如何工作?
标签: