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


78

通常使用以下方法之一来防止跨站请求伪造(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函数。仅当两个值相等时,该操作才被授权。

尽管事实上如果没有JavaScript也无法使用,这种方法有什么问题吗?

附录:


您的用户秘密并非用户独有,攻击者只需要获取该数字并调整其脚本以使用新的计算即可。如果您根本没有状态,您将如何认证用户?
Mike

用户密码对于每个用户而言都是唯一的,并且只能在身份验证(HTTP基本身份验证或摘要身份验证或证书身份验证)后检索
守护进程2010年

Answers:


28

这里有很多答案,其中很多都是问题。

您不应该做的事情:

  1. 如果您需要从JavaScript中读取会话令牌,那么您就在做错了什么。您的会话标识符cookie应当始终设置为HTTPOnly,因此脚本不可用。

    这一保护措施可以大大降低XSS的影响,因为攻击者将不再能够获得登录用户的会话令牌,该令牌对于所有意图和目的均等同于应用程序中的凭据。您不希望出现一个错误来提供王国的密钥。

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

您应该做的事情:

  1. 遵循OWASP的指导

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

只需创建一个随机加密的东西,将其存储为ASCII十六进制或Base64编码,然后在服务器返回页面时将其作为cookie并添加到您的表单中。在服务器端,请确保Cookie值与表单值匹配。瞧,您已经杀死了CSRF,避免给用户带来额外的提示,也没有让自己面临更多的漏洞。

注意:如@krubo所述,下面已经发现重复提交技术存在一些缺点(请参阅重复提交)。由于此弱点要求:

  1. 您定义范围为父域的cookie。
  2. 您无法设置HSTS
  3. 攻击者控制用户和服务器之间的某些网络位置

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


新更新07/06/2020

我最喜欢做双重提交的方法是像以前一样在请求的主体中创建并传递一个加密的随机字符串。而不是让cookie是完全相同的值,而是让cookie是由证书签名的字符串的编码值。在服务器端验证仍然很容易,但是攻击者很难模仿。您仍然应该使用我的文章前面概述的samesite Cookie属性和其他保护措施。


带有会话Cookie的XSS与带有可从JavaScript读取的令牌的XSS一样容易受到攻击。如果仍然可以制定一个AJAX请求,将用户帐户中的资金转入我的帐户,服务器将很乐意接受。
ghayes

4
@我同意。您的会话令牌比CSRF令牌敏感得多。使用您的会话令牌,我可以像您一样从我的计算机上完全访问该应用程序。使用CSRF令牌,我可以潜在地获得在浏览器中执行的预定义敏感动作列表。第二种情况很难实现,需要了解应用程序,需要更长的时间才能执行,并且操作仅限于您事先计划的操作。第一种情况对于任何网站都使用一行代码,并使用Cookie Manager应用程序供攻击者在其计算机上使用。
道格

在这里值得一提。cross-origin HTTP request对服务器和API的http返回标头进行严格检查,可以限制攻击者可能对登录用户造成的大量自动损害。
LessQuesar

1
更新OWASP指导不再接受双提交CSRF的令牌作为主要防御,但它转移到防御纵深。问题在于,如果攻击者可以编写cookie,那么它就可能被击败,例如,如果他们控制另一个子域,他们就可以这样做。
krubo

1
OWASP链接现在返回404
ParkerD

10

我说对了吗?

  • 您希望针对通过Cookie登录的用户提供CSRF保护。
  • 同时,您需要针对应用程序的Basic,OAuth和Digest身份验证请求的RESTful接口。

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

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

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


我也想知道这个!根据本文mathieu.fenniak.net/…,应该可以打开CSRF,以检查是否有人通过cookie /会话进入,如果请求通过某种无状态身份验证方案Basic,则将其关闭。等
CMCDragonkai 2013年

4
使用基本身份验证时要小心-它实际上等效于通过cookie登录的用户,因为浏览器将在后续请求中发送提供的Authorization标头,以方便用户。
Simon Lieschke

@SimonLieschke将集成Windows / ntlm / kerberos。如果设置,浏览器将仅从DC获得令牌。
stuartm9999

9

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

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

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

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

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


1
HTTP身份验证证明,身份验证不需要会话。生成令牌的JavaScript代码不是秘密的-只有用户秘密必须是秘密的。
迪蒙

3
@Sri尽管我同意从安全性和性能上来说,会话​​是处理此问题的最佳方法。这不是RESTful的,因为它要求服务器跟踪每个用户的状态,这可能会导致可伸缩性问题。
rook 2012年

1
说如果页面容易受到XSS攻击,则双重Cookie提交将不起作用是正确的说法吗?因为这样您就可以直接从域本身内部提交表单,并且该值将同时通过Cookie和表单发送。
加布里埃里·西鲁利

1
@GabrieleCirulli是的,这是公平的声明。XSS击败了大多数CSRF保护。验证码可能是唯一仍然有效的CSRF形式。
Sripathi Krishnan 2012年

您是说CSRF保护?:P但是是的,我同意。
加布里埃尔·西鲁里

6

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

我不确定我是否完全理解建议的防御措施;哪里GET /usersecret/john_doe来的?页面的那部分是JavaScript吗?这是文字提议的URL吗?如果是这样,我假设这username不是秘密,这意味着如果浏览器或插件错误允许跨域GET请求,evil.ru可以恢复用户秘密。为什么不通过身份验证将用户机密存储在Cookie中,而不是让任何能够进行跨域GET的人都将其检索呢?

在实施自己的对CSRF有抵抗力的身份验证系统之前,我会非常认真地阅读“针对跨站点伪造的鲁棒防御”。实际上,我会重新考虑完全实现自己的身份验证系统。


1
表单ID类似于公共密钥。没错,这GET /usersecret/john_doe是JavaScript的一部分。用户名本身不是秘密,而是经过身份验证的(!)用户随此请求获取的ID。谢谢你的链接。
守护进程

3

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


2
这是危险的建议,虽然很难欺骗HTTP引用程序,但这并非不可能,而且不能保证引用程序头ist(并且不发送引用程序头会破坏您的应用程序)。
Boy Baukema

3
@RelaXNow看看我写的这个CSRF利用框架:github.com/TheRook/CSRF-Request-Builder。它允许您指定任意的http标头以及正文。但是,它不能更改http引用,因为Flash禁止了它。CSRF预防备忘单非常好,您应该阅读我帖子中的链接。
菜鸟2012年

公平地说,就CSRF而言,攻击者将无法(据我所知)欺骗受害者的Referer标头,但是仍不能保证标头,并且仅在可以的情况下才需要对API进行标头确保始终将其发送(例如公司内部申请)。
Boy Baukema

3
@RelaXNow如果请求源自HTTPS页面,则将根据请求提交引用。应该被视为失败(在上面的链接中提到)。人们正在研究这个问题,Mozilla引入了“ Origin” http标头,该标头很棒并且值得研究,它不仅可以用来解决RESTful csrf保护这一问题,而且还可以滥用许多其他滥用方式,例如json包含攻击和点击劫持。问题是,并不是每个浏览器都支持它:(。另外,我编辑了我的文章,以防万一您想摆脱
-1。– rook

1
s / comitted /省略/ :)。好点/信息,但很久以前我撤回了-1,并就您的评论获得有用的信息。
Boy Baukema

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,这是非常基本的技能。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.