我正在编写一个应用程序(Django,它确实发生了),我只想了解“ CSRF令牌”实际上是什么以及它如何保护数据。如果不使用CSRF令牌,发布数据是否不安全?
我正在编写一个应用程序(Django,它确实发生了),我只想了解“ CSRF令牌”实际上是什么以及它如何保护数据。如果不使用CSRF令牌,发布数据是否不安全?
Answers:
www.mybank.com
mybank.com
将导致(在概念上)形式的要求http://www.mybank.com/transfer?to=<SomeAccountnumber>;amount=<SomeAmount>
。(不需要您的帐号,因为您的登录名暗示了该帐号。)www.cute-cat-pictures.org
,却不知道这是一个恶意网站。mybank.com
(需要运气!),则他们可以在其页面上添加一个请求,例如http://www.mybank.com/transfer?to=123456;amount=10000
(123456
开曼群岛帐户的编号在哪里) ,这10000
是您以前认为很高兴拥有的金额)。www.cute-cat-pictures.org
页面,那么你的浏览器会作出这样的要求。www.mybank.com
cookie,并且看起来完全合法。你的钱去了!这是没有CSRF令牌的世界。
现在,使用 CSRF令牌获得更好的效果:
http://www.mybank.com/transfer?to=123456;amount=10000;token=31415926535897932384626433832795028841971
。mybank.com
当他们将其提供给您时会包含在他们自己的网页上。每次他们向任何人提供任何页面时,情况都不同。www.mybank.com
。结果:您保留了10000
货币单位。我建议您将其中一些捐赠给Wikipedia。
(你的旅费可能会改变。)
编辑评论值得阅读:
值得注意的是www.cute-cat-pictures.org
,www.mybank.com
由于HTTP访问控制,通常来自的脚本无法访问您的反CSRF令牌。对于某些人,他们Access-Control-Allow-Origin: *
不了解每个网站响应的标题而仅仅因为他们不能使用另一个网站的API,因此对于那些不合理地为每个网站响应发送标头的人来说非常重要。
www.cute-cat-pictures.org
,www.mybank.com
由于HTTP访问控制,通常来自的脚本无法访问您的反CSRF令牌。对于某些人,他们Access-Control-Allow-Origin: *
不了解每个网站响应的标题而仅仅因为他们不能使用另一个网站的API,因此对于那些不合理地为每个网站响应发送标头的人来说非常重要。
是的,帖子数据是安全的。但是该数据的来源不是。这样,当浏览攻击者的网页时,有人可以欺骗JS用户登录到您的站点。
为了防止这种情况,django将在cookie和表单数据中发送一个随机密钥。然后,当用户POST时,它将检查两个密钥是否相同。万一被用户欺骗,第三者网站将无法获取您网站的Cookie,从而导致身份验证错误。
该网站在制作表单页面时会生成一个唯一的令牌。需要此令牌才能将数据发布/获取回服务器。
由于令牌是由您的网站生成的,并且仅在生成带有表单的页面时提供,因此其他网站无法模仿您的表单-他们没有令牌,因此无法发布到您的网站。
假设您在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),则
http://a.com/tweet
当用户单击“提交”按钮时,便会像往常一样发送POST请求并进行 处理。到目前为止,只要让用户知道该表单的确切功能,这就不是什么大问题,但是如果我们的坏蛋像这样调整表单,该怎么办:
<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应用程序不区分POST和GET请求(例如,在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请求实际上是一个POST请求,则必须确保仅绕过Web应用程序中的CSRF检查。 AJAX请求。您可以通过查找AJAX请求通常包含的标头(如X-Requested-With)来实现。您还可以设置另一个自定义标头,并检查它在服务器端是否存在。这是安全的,因为浏览器不会将自定义标头添加到常规HTML表单提交中(请参见上文),因此Bad Guy先生没有机会使用表单模拟此行为。
如果您对AJAX请求有疑问,由于某种原因,您不能检查X-Requested-With之类的标头,只需将生成的CSRF令牌传递给JavaScript,然后将该令牌添加到AJAX请求中。有几种方法可以做到这一点。要么像常规HTML表单一样将其添加到有效负载中,要么将自定义标头添加到AJAX请求中。只要您的服务器知道要在传入请求中查找的位置,并且能够将其与从会话或cookie中记住的原始值进行比较,就可以对您进行排序。
这一切的根本是确保请求来自站点的实际用户。将为表单生成一个csrf令牌,并且该令牌必须绑定到用户的会话。它用于将请求发送到服务器,令牌在其中验证请求。这是一种防止csrf的方法,另一种方法是检查引荐来源标头。