我的CSRF保护方法安全吗?

3
我一直在使用PHP进行自己的CSRF保护。根据我所了解的,我决定使用cookie来实现我的保护,但是我有些困惑,不确定我的方法是否安全防范CSRF攻击。
我的方法如下:
1. 用户发送登录请求。 2. 服务器检查是否设置了CSRF令牌,如果没有,则创建一个并将其存储在会话中,并创建一个带有令牌的Cookie。 3. 通过检查POST请求中是否存在CSRF令牌来验证CSRF令牌,如果不存在,则检查$_COOKIE中的令牌。 4. 如果令牌无效,则返回消息......
我决定使用cookie来存储令牌,因为这样对于Ajax请求将起作用,我不必每次使用Ajax POST时都要包含它。
我困惑的是,攻击者难道不可以只是发送一个请求;POST或GET,因为cookie已经存在,它就会随着请求被发送,因此每次都会发送令牌与浏览器一起发送,从而成为有效请求吗?

CodeReview可能更适合你。 - Script47
浏览器将阻止跨站点请求,因此任何具有有效CSRF令牌的请求通常来自于浏览器访问您自己的域(除非您在某些路由中启用了CORS,在这种情况下您需要小心)。 - apokryfos
@t.niese 一款合规的浏览器必须阻止跨域请求,除非它们符合某些严格的规则(请参见 https://www.w3.org/TR/cors 的第7.2节)。具体来说:如果响应包括零个或多个Access-Control-Allow-Origin头值,则返回失败并终止此算法。 Access-Control-Allow-Origin 头是否设置完全取决于开发人员的决定。JSONP请求可能是一个例外。 - apokryfos
@apokryfos 的CSRF存在于保护请求不受一般不尊重CORS的浏览器的影响,或者对于当前所有浏览器都允许的表单POST/ GET和脚本加载(jsonp)等请求(如果这符合规范,则无关紧要)。 如果所有浏览器都尊重CORS,并且如果跨域表单POST不被规范允许,则CSRF将不相关。 但由于跨域表单POST是可能的,因此需要CSRF令牌,并且仅当它们未存储在客户端的cookie中时才具有任何额外的会话cookie安全性。 - t.niese
1
@apokryfos 相关部分涵盖了以下内容:[...]根据本规范之外生成的简单跨源请求(例如使用GET或POST进行的跨源表单提交或由脚本元素引起的跨源GET请求)通常包括用户凭据,因此符合本规范的资源必须始终准备好接受带有凭据的简单跨源请求。[...]4 安全注意事项 - t.niese
显示剩余2条评论
2个回答

4

Cookie不应该包含CSRF令牌,只有客户端会话存储应该包含它。而且您不应该反对存在于cookie中的CSRF。

如果您检查发送到cookie的CSRF,那么您将绕过CSRF背后的想法。

一个简单的攻击场景是在外部网站上隐藏表单:

<form method="method" action="http://site-to-gain-access-to.tld/someactionurl">
  <!-- some form data -->
</form>

这个隐藏的表单将在用户不干预的情况下使用JavaScript执行。如果用户已登录到"site-to-gain-access-to.tld"网站,并且没有启用CSRF保护,则就好像用户自己触发了该操作一样,因为用户的会话cookie将随该请求发送。因此,服务器会认为是用户触发了该请求。
如果您现在将CSRF令牌放入cookie中,则会遇到与会话相同的问题。
CSRF令牌必须始终作为请求正文或URL的一部分发送,而不是作为cookie发送。
所以这些部分:
服务器检查是否设置了CSRF令牌,如果没有则创建一个并将其存储在其会话中,并创建一个带有令牌的Cookie 通过检查POST请求中是否有CSRF令牌来验证CSRF令牌,如果没有,则在$_COOKIE中检查令牌
会破坏CSRF保护。无论CSRF是以纯文本还是具有服务器端加密的形式存储在cookie中都无关紧要。

1
是的,您应该将其作为表单中的隐藏输入包含,并与服务器端会话存储进行比较。这是使您的网站安全的一部分。您肯定需要确保您的网站再次受到XSS攻击的保护,否则恶意脚本可能能够访问CSRF令牌。因此,会话ID确认请求是由用户的浏览器完成的,而CSRF确认请求是由用户完成的。 - t.niese
好的,这很有道理。但是对于Ajax请求,包含位于页面上的CSRF令牌是否仍然安全,因为我的一些Ajax请求不是来自被POST的表单? - Erdss4
1
@Erdss4 是的,你可以通过ajax请求传递CSRF令牌,作为请求体中的属性或者作为一个带有x-前缀的头部,例如X-CSRF-Token。个人建议使用x-前缀的头部,因为它不会干扰你的请求数据。 - t.niese
在您的网页中,您可以添加令牌,例如 <html data-csrf-token="[the token]">,然后您可以在每次请求时访问该令牌。 - t.niese
1
@Erdss4,你可以在HTML元素上添加一个data attribute(使用数据属性](https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Howto/Use_data_attributes)). 这些可以在现代浏览器中直接访问nodeElement.dataset或在类库中访问,例如:jQuery.data() - t.niese
显示剩余2条评论

2
只要“用户发送登录请求”指的是获取登录页面的初始GET请求,这是正确的。CSRF应该在GET请求中生成和存储,并在每个POST请求期间进行验证。
引用:
我困惑的是攻击者不是可以随便发起一个请求,无论是POST还是GET,因为cookie已经存在,它就会随着请求一起发送,因此成为有效的请求,因为令牌每次都与浏览器一起发送?
是的。CSRF令牌并不是秘密。它们只是确认只有在预期的GET请求之后才会进行POST。这意味着某人只能在首先请求表单后提交表单。它防止恶意网站通过POST将用户发送到您的网站。它不能防止攻击者发出GET请求、抓取令牌并进行有效的POST。
来自OWASP:OWASP 通过社交工程的一点帮助(例如通过电子邮件或聊天发送链接),攻击者可能会欺骗 Web 应用程序的用户执行攻击者选择的操作。如果受害者是普通用户,则成功的 CSRF 攻击可以强制该用户执行状态更改请求。

如果我只是在每个表单中包含我的令牌作为隐藏输入,并使用存储在SESSION变量中的令牌验证令牌,这就不够了吗?你是说令牌还应该是表单操作中GET请求的一部分吗? - Erdss4
在隐藏的输入元素中包含令牌就足够了(只要您的网站不会在GET请求上更改数据)。我正在解释令牌是在GET请求期间生成的(以检索带有表单的页面)。然后,您可以使用cookie或隐藏的输入元素再次提交令牌以进行POST。 - Matt S
那么你可以忽略在表单中放置隐藏输入框,仅依赖于每次发送的 cookie 吗? - Erdss4
Cookies需要一些额外的考虑。我在其他人的评论和帖子中看到了它们的列表。还请阅读我提供的OWASP链接相关页面。 - Matt S

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接