如何在RESTful应用程序中阻止CSRF?

时间:2010-03-06 10:02:49

标签: security http rest authorization csrf

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

  • 检查引用 - RESTful但不可靠
  • 将令牌插入表单并将令牌存储在服务器会话中 - 而不是真正的RESTful
  • 神秘的一次性URI--由于与令牌相同的原因而不是RESTful
  • 手动为此请求发送密码(不是HTTP身份验证使用的缓存密码) - 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函数并比较两个结果。只有当两个值相等时,才会授权操作。
  6. 这种方法有问题,尽管如果没有JavaScript它不起作用吗?

    附录:

6 个答案:

答案 0 :(得分:24)

这里有很多答案,还有很多问题。

你不应该做的事情:

  1. 如果您需要从JavaScript中读取会话令牌,那么您就会做出可怕的错误。您的会话标识符cookie应始终在其上设置HTTPOnly,因此脚本无法使用它。

    这一保护使得XSS的影响大大降低,因为攻击者将无法再获得登录的用户会话令牌,这对于所有意图和目的而言都等同于应用程序中的凭据。你不希望有一个错误给王国钥匙。

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

  3. 你应该做的事情:

    1. 关注OWASP's guidance

    2. 具体来说,如果这是一个REST应用程序,您可以require double-submission of CSRF tokens

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

答案 1 :(得分:10)

我是否正确:

  • 您希望通过Cookie登录的用户免受CSRF的侵害。
  • 与此同时,您需要RESTful接口,以便从应用程序获得基本,OAuth和Digest经过身份验证的请求。

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

我不确定是否可以让其他网站伪造基本身份验证或标题?

据我所知,CSRF 所有关于cookie ? Cookie不会发生RESTful身份验证。

答案 2 :(得分:9)

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

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

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

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

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

答案 3 :(得分:6)

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

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

在我实现自己的身份验证系统之前,我会非常仔细地阅读"Robust Defenses for Cross-Site Forgery",我希望能够抵制CSRF。事实上,我会重新考虑实现我自己的身份验证系统。

答案 4 :(得分:3)

CSRF Prevention Cheat Sheet中有一些方法可供宁静服务使用。最RESTful无状态CSRF缓解是使用Origin或HTTP referer来确保请求来自您信任的域。

答案 5 :(得分:0)

  

这种方法有问题,尽管事实如此   没有JavaScript会不起作用?

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

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

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

  • 您可以使用REST服务添加csrf令牌cookie,并使用资源的表示形式发送相同的令牌(隐藏的输入等...)。其他所有内容都与上一个解决方案的结尾相同。这个解决方案处于RESTfulness的边缘。 (没有问题,直到客户端不调用服务来修改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,这是非常基本的技能。