The Cloud Under blog has a good explanation of CSRF tokens.(已存档)
想象一下,您有一个类似简化版 Twitter 的网站,托管在 a.com 上。
登录的用户可以将一些文本(一条推文)输入到正在生成的表单中
作为 POST 请求发送到服务器,并在它们命中时发布
提交按钮。在服务器上,用户由 cookie 标识
包含他们唯一的会话 ID,因此您的服务器知道谁发布了
推文。
表格可以这么简单:
<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>
现在,如果您的某个用户最终访问了坏人的网站并点击了
“点击赢!”按钮,表单提交到
您的网站,用户可以通过会话 ID 正确识别
cookie 和隐藏的推文被发布。
如果我们的坏人更坏,他会让无辜的用户提交
当他们使用 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 令牌:
将静态源代码从我们的页面复制到其他网站
将无用,因为隐藏字段的值随
每个用户。没有坏人的网站知道当前用户的
您的服务器将始终拒绝 POST 请求的 CSRF 令牌。
因为坏人的恶意页面是由您用户的浏览器加载的
来自不同的域(b.com 而不是 a.com),坏人没有
有机会编写一个 JavaScript 来加载内容,因此我们的
用户当前来自您网站的 CSRF 令牌。那是因为网络
浏览器默认不允许跨域 AJAX 请求。
坏人也无法访问你服务器设置的cookie,
因为域不匹配。
我应该什么时候防止跨站点请求伪造?如果你可以的话
确保不要将 GET、POST 和其他请求方法混为
如上所述,一个好的开始是通过以下方式保护所有 POST 请求
默认。
您不必保护 PUT 和 DELETE 请求,因为
如上所述,浏览器无法提交标准 HTML 表单
使用这些方法。
另一方面,JavaScript 确实可以发出其他类型的请求,
例如使用 jQuery 的 $.ajax() 函数,但请记住,对于 AJAX 请求
工作域必须匹配(只要你没有明确
否则配置您的网络服务器)。
这意味着,通常您甚至不必将 CSRF 令牌添加到 AJAX
请求,即使它们是 POST 请求,但您必须进行
确保仅在以下情况下绕过 Web 应用程序中的 CSRF 检查
POST 请求实际上是一个 AJAX 请求。你可以这样做
寻找像 X-Requested-With 这样的标头的存在,它是 AJAX
请求通常包括。您还可以设置另一个自定义标题和
检查它在服务器端的存在。这是安全的,因为
浏览器不会将自定义标题添加到常规 HTML 表单提交中
(见上文),所以坏人先生没有机会模拟这种行为
用表格。
如果您对 AJAX 请求有疑问,因为出于某种原因,您
无法检查像 X-Requested-With 这样的标头,只需传递
生成 CSRF 令牌到您的 JavaScript 并将令牌添加到 AJAX
要求。有几种方法可以做到这一点;要么将其添加到
有效载荷就像一个常规的 HTML 表单一样,或者添加一个自定义标题到
AJAX 请求。只要您的服务器知道在哪里寻找它
一个传入的请求,并且能够将它与它的原始值进行比较
从会话或 cookie 中记住,您已排序。