80

通常使用以下方法之一来防止跨站请求伪造 (CSRF):

  • 检查referer - RESTful 但不可靠
  • 将令牌插入表单并将令牌存储在服务器会话中 - 不是真正的 RESTful
  • 神秘的一次性 URI - 不是 RESTful,原因与令牌相同
  • 为此请求手动发送密码(不是用于 HTTP auth 的缓存密码)- RESTful 但不方便

我的想法是使用用户密码、神秘但静态的表单 ID 和 JavaScript 来生成令牌。

<form method="POST" action="/someresource" id="7099879082361234103">
    <input type="hidden" name="token" value="generateToken(...)">
    ...
</form>
  1. GET /usersecret/john_doe由 JavaScript 从经过身份验证的用户那里获取。
  2. 回应:OK 89070135420357234586534346这个秘密在概念上是静态的,但可以每天/每小时更改......以提高安全性。这是唯一保密的事情。
  3. 使用 JavaScript 读取神秘的(但对所有用户都是静态的!)表单 ID,将其与用户密码一起处理:generateToken(7099879082361234103, 89070135420357234586534346)
  4. 将表单连同生成的令牌一起发送到服务器。
  5. 由于服务器知道用户密码和表单 id,因此可以在发送之前运行与客户端相同的 generateToken 函数并比较两个结果。只有当两个值相等时,才会授权该操作。

这种方法有问题吗,尽管没有 JavaScript 就无法工作?

附录:

4

6 回答 6

32

这里有很多答案,其中有不少问题。

你不应该做的事情:

  1. 如果您需要从 JavaScript 读取会话令牌,那么您做错了什么。您的会话标识符 cookie 应始终在其上设置 HTTPOnly,以便脚本无法使用它。

    这种保护使得 XSS 的影响大大减少,因为攻击者将不再能够获得登录用户的会话令牌,这在所有意图和目的上都相当于应用程序中的凭据。你不希望一个错误给王国的钥匙。

  2. 会话标识符不应写入页面内容。这与您设置 HTTPOnly 的原因相同。这意味着您的 csrf 令牌不能是您的会话 ID。它们需要是不同的值。

你应该做的事情:

  1. 遵循OWASP 的指导

  2. 具体来说,如果这是一个 REST 应用程序,您可以要求双重提交 CSRF 令牌。如果您这样做,只需确保将其定义为特定的完整域 ( www.mydomain.com ) 而不是父域 (example.com),并且您还利用了正在获得的“samesite”cookie 属性人气。

只需创建加密随机的内容,将其存储为 ASCII Hex 或 Base64 编码,然后在服务器返回页面时将其作为 cookie 添加到您的表单中。在服务器端确保 cookie 值与表单值匹配。瞧,您已经杀死了 CSRF,避免了为您的用户提供额外提示,并且没有让自己面临更多漏洞。

注意:正如@krubo 所述,已发现双重提交技术存在一些弱点(请参阅双重提交)。由于这个弱点要求:

  1. 您定义一个限定为父域的 cookie。
  2. 您未能设置 HSTS
  3. 攻击者控制用户和服务器之间的一些网络位置

我有点认为这个弱点更多地属于“Cool Defcon Talk”而不是“现实世界的安全风险”。无论如何,如果您要使用双重提交,那么采取一些额外的步骤来充分保护自己并没有什么坏处。


新更新 07/06/2020

我最喜欢的双重提交方式是像以前一样在请求正文中创建并传递一个加密随机字符串;但与其让 cookie 具有相同的确切值,不如让 cookie 成为由证书签名的字符串的编码值。这在服务器端仍然很容易验证,但对于攻击者来说更难模仿。您仍应使用我之前文章中概述的相同站点 Cookie 属性和其他保护措施。

于 2015-09-21T16:31:51.920 回答
11

我做对了吗:

  • 您希望保护通过 cookie 登录的用户免受 CSRF 的影响。
  • 同时,您需要 RESTful 接口来处理来自应用程序的 Basic、OAuth 和 Digest 认证请求。

那么,为什么不检查用户是否通过 cookie 登录应用 CSRF呢?

我不确定,但其他网站是否有可能伪造基本身份验证或标头之类的东西?

据我所知,CSRF都是关于 cookie的吗?cookie 不会发生 RESTful 身份验证。

于 2013-05-27T19:18:56.293 回答
9

您肯定需要服务器上的某些状态来进行身份验证/授权。不过,它不一定是 http 会话,您可以将其存储在分布式缓存(如 memcached)或数据库中。

如果您使用 cookie 进行身份验证,最简单的解决方案是重复提交 cookie 值。在提交表单之前,从 cookie 中读取会话 id,将其存储在隐藏字段中,然后提交。在服务器端,确认请求中的值与会话 id(您从 cookie 中获得的)相同。来自另一个域的邪恶脚本将无法从 cookie 中读取会话 id,从而阻止 CSRF。

此方案在整个会话中使用单个标识符。

如果您想要更多保护,请为每个会话生成一个唯一的 ID。

另外,不要在 JS 中生成令牌。任何人都可以复制代码并从不同的域运行它来攻击您的站点。

于 2010-03-07T06:25:19.580 回答
6

静态表单 ID 根本没有提供任何保护;攻击者可以自己获取它。请记住,攻击者不限于在客户端使用 JavaScript;他可以在服务器端获取静态表单 ID。

我不确定我是否完全理解提议的辩护;从哪里来GET /usersecret/john_doe?页面的那部分是 JavaScript 吗?那是字面建议的网址吗?如果是这样,我假设这username不是秘密,这意味着如果浏览器或插件错误允许跨域 GET 请求,evil.ru 可以恢复用户秘密。为什么不在身份验证时将用户密码存储在 cookie 中,而不是让任何可以进行跨域 GET 的人检索它?

在我实现自己的身份验证系统之前,我会非常仔细地阅读“跨站点伪造的强大防御” ,我希望能够抵抗 CSRF。事实上,我会重新考虑实施我自己的身份验证系统。

于 2010-03-06T20:24:04.137 回答
3

CSRF 预防备忘单中有一些方法可以供 restful 服务使用。最 RESTful 的无状态 CSRF 缓解措施是使用 Origin 或HTTP 引用来确保请求来自您信任的域。

于 2012-04-06T00:18:29.083 回答
0

这种方法有问题吗,尽管没有 JavaScript 就无法工作?

如果您将用户密码发送给客户端,则您的用户密码不是密码。我们通常使用这样的秘密来生成哈希并与表单一起发送,然后等待它们返回进行比较。

如果您想成为 RESTful,则请求必须包含有关如何处理它的所有信息。你可以这样做的方法:

  • 使用您的 REST 客户端添加一个 csrf 令牌 cookie,并使用您的表单在隐藏输入中发送相同的令牌。如果服务和客户端在不同的域下,您必须共享凭据。在服务上,您必须比较 2 个令牌,如果它们相同,则请求有效......

  • 您可以将 csrf 令牌 cookie 添加到您的 REST 服务中,并使用您的资源表示(隐藏输入等)发送相同的令牌。其他一切都与上一个解决方案的结尾相同。这个解决方案处于 RESTful 的边缘。(在客户端不调用服务修改cookie之前是可以的。如果cookie只是http,客户端应该不知道它,如果不是,那么客户端应该设置它。)你可以做更多如果您向每个表单添加不同的令牌并将过期时间添加到 cookie,则解决方案很复杂。您也可以将过期时间与表单一起发回,这样您就会知道令牌验证失败的原因。

  • 您可以在服务的资源状态中拥有一个用户密码(每个用户不同)。通过构建表示,您可以为每个表单生成一个令牌(和到期时间)。您可以从实际令牌(以及到期时间、方法、url 等)和用户密码生成哈希,并将该哈希与表单一起发送。当然,您将“用户机密”保密,因此您永远不会将其与表单一起发送。之后,如果您的服务收到请求,您可以再次从请求参数和用户密码生成哈希,并进行比较。如果不匹配,则请求无效...

如果您的 REST 客户端是 javascript 可注入的,它们都不会保护您,因此您必须根据 HTML 实体检查所有用户内容,并删除所有这些内容,或者始终使用 TextNodes 而不是 innerHTML。您还必须保护自己免受 SQL 注入和 HTTP 标头注入。永远不要使用简单的 FTP 来刷新您的站点。等等...有很多方法可以将恶意代码注入您的站点...

我差点忘了提,GET 请求总是由服务和客户端读取。通过服务,这很明显,通过客户端在浏览器中设置的任何 url 必须导致一个或多个资源的表示,它不应该在资源上调用 POST/PUT/DELETE 方法。例如GET http://my.client.com/resource/delete -> DELETE http://my.api.com/resource,这是一个非常非常糟糕的解决方案。但如果你想阻碍 CSRF,这是非常基本的技能。

于 2013-12-05T10:12:19.270 回答