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

时间:2023-01-06 05:05:20

Cross Site Request Forgery (CSRF) is typically prevent with one of the following methods:

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

  • Check referer - RESTful but unreliable
  • 检查引用 - RESTful但不可靠
  • insert token into form and store the token in the server session - not really RESTful
  • 将令牌插入表单并将令牌存储在服务器会话中 - 而不是真正的RESTful
  • cryptic one time URIs - not RESTful for the same reason as tokens
  • 神秘的一次性URI - 由于与令牌相同的原因而不是RESTful
  • send password manually for this request (not the cached password used with HTTP auth) - RESTful but not convenient
  • 手动为此请求发送密码(不是HTTP身份验证使用的缓存密码) - RESTful但不方便

My idea is to use a user secret, a cryptic but static form id and JavaScript to generate tokens.

我的想法是使用用户秘密,一个神秘但静态的表单id和JavaScript来生成令牌。

<form method="POST" action="/someresource" id="7099879082361234103">
    <input type="hidden" name="token" value="generateToken(...)">
    ...
</form>
  1. GET /usersecret/john_doe fetched by the JavaScript from the authenticated user.
  2. GET / usersecret / john_doe由来自经过身份验证的用户的JavaScript提取。
  3. Response: OK 89070135420357234586534346 This secret is conceptionally static, but can be changed every day/hour ... to improve security. This is the only confidential thing.
  4. 回复:OK 89070135420357234586534346这个秘密在概念上是静态的,但可以每天/每小时更改......以提高安全性。这是唯一保密的事情。
  5. Read the cryptic (but static for all users!) form id with JavaScript, process it together with the user secret: generateToken(7099879082361234103, 89070135420357234586534346)
  6. 使用JavaScript阅读神秘的(但所有用户都是静态的!)表单ID,与用户秘密一起处理:generateToken(7099879082361234103,89070135420357234586534346)
  7. Send the form along with the generated token to the server.
  8. 将表单与生成的令牌一起发送到服务器。
  9. Since the server knows the user secret and the form id, it is possible to run the same generateToken function as the client did before sending and compare both results. Only when both values are equal the action will be authorized.
  10. 由于服务器知道用户密钥和表单ID,因此可以在发送之前运行与客户端相同的generateToken函数并比较两个结果。只有当两个值相等时,才会授权操作。

Is something wrong with this approach, despite the fact that it doesn't work without JavaScript?

这种方法有问题,尽管如果没有JavaScript它不起作用吗?

Addendum:

附录:

6 个解决方案

#1


22  

There are a lot of answers here, and problems with quite a few of them.

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

Things you should NOT do:

你不应该做的事情:

  1. If you need to read the session token from JavaScript, you're doing something horribly wrong. Your session identifier cookie should ALWAYS have HTTPOnly set on it so its not available to scripts.

    如果您需要从JavaScript中读取会话令牌,那么您正在做一些可怕的错误。您的会话标识符cookie应始终在其上设置HTTPOnly,因此脚本不可用。

    This one protection makes it so that the impact of XSS is considerably reduced, since an attacker will no longer be able to get a logged in users session token, which for all intents and purposes are the equivalent of credentials in the application. You don't want one error to give keys to the kingdom.

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

  2. The session identifier should not be written to the contents of the page. This is for the same reasons you set HTTPOnly. This means that that your session token can not be your session id. They need to be different values.

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

Things you should do:

你应该做的事情:

  1. Follow OWASP's guidance:

    遵循OWASP的指导:

  2. Specifically, if this is a REST application you can require double-submission of CSRF tokens:

    具体来说,如果这是一个REST应用程序,您可以要求提交CSRF令牌的双重提交:

Simply create something cryptographically random, store it in ASCII Hex or Base64 encode, and add it as a cookie and to your forms when the server returns the page. On the server side make sure that the cookie value matches the form value. Voila, you've killed CSRF, avoided extra prompts for your users, and not opened yourself up to more vulnerabilities.

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

#2


9  

You definitely need some state on the server to authenticate/authorize. It need not be the http session though, you could store it in a distributed cache (like memcached) or a database.

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

If you use cookies for authentication, the easiest solution is to double-submit the cookie value. Before you submit the form, read the session id from the cookie, store it in a hidden field and then submit it. On the server side, confirm that the value in the request is the same as the session id (that you got from the cookie). Evil script from another domain will not be able to read the session id from the cookie, thus preventing CSRF.

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

This scheme uses a single identifier across the session.

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

If you want more protection, generate a unique id per-session per-form.

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

Also, DO NOT generate tokens in JS. Anybody can copy the code and run it from a different domain to attack your site.

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

#3


8  

Am I getting this right:

我是否正确:

  • You want protection against CSRF for users logged in via cookies.
  • 您希望通过cookie登录的用户免受CSRF的攻击。
  • And at the same time you want RESTful interface for Basic, OAuth and Digest authenticated requests from apps.
  • 同时,您需要来自应用程序的基本,OAuth和Digest经过身份验证的请求的RESTful接口。

So, why not check whether users is logged in via cookie and apply CSRF only then?

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

I'm not sure but is possible for another site to forge things like Basic auth or headers?

我不确定但是其他网站是否有可能伪造像Basic auth或者头文件这样的东西?

As far as I know , CSRF is all about cookies? RESTful auth doesn't happen with cookies.

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

#4


6  

The static form ID provides no protection at all; an attacker can fetch it himself. Remember, the attacker is not constrained to using JavaScript on the client; he can fetch the static form ID server-side.

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

I'm not sure I entirely understand the proposed defense; where does the GET /usersecret/john_doe come from? Is that part of the page JavaScript? Is that the literal proposed URL? If so, I'm assuming that username is not a secret, which means that evil.ru can recover user secrets if a browser or plugin bug allows cross-domain GET requests. Why not store the user secret in a cookie upon authentication rather than let anyone who can do cross-domain GETs retrieve it?

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

I would read "Robust Defenses for Cross-Site Forgery" really carefully before I implemented my own authentication system that I wanted to be resistant to CSRF. In fact, I would reconsider implementing my own authentication system at all.

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

#5


3  

There are a few methods in the CSRF Prevention Cheat Sheet that can be used by restful service. The most RESTful stateless CSRF mitigation is using the Origin or HTTP referer to make sure the requests originate from a domain you trust.

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

#6


0  

Is something wrong with this approach, despite the fact that it doesn't work without JavaScript?

这种方法有问题,尽管如果没有JavaScript它不起作用吗?

Your user secret is not a secret if you send it to the client. We usually use such secrets to generate hashes and send them with the form, and wait them back for comparison.

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

If you want to be RESTful, the request has to contain every information about how to process it. The ways you can do this:

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

  • Add a csrf token cookie with your REST client and send the same token in hidden input with your forms. If the service and the client are under different domains, you have to share the credentials. On the service you have to compare the 2 tokens, and if they are the same, the request is valid...

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

  • You can add the csrf token cookie with your REST service and send the same token with the representations of your resources (hidden inputs, etc...). Everything else is the same as the end of the previous solution. This solution is on the edge of RESTfulness. (It is okay until the client do not call the service to modify the cookie. If the cookie is http only, the client should not know about it, if it is not, then the client should set it.) You can do a more complex solution if you add different tokens to each forms and add expiration time to the cookies. You can send the expiration time back with the forms as well, so you will know the reason when a token validation fails.

    您可以使用REST服务添加csrf令牌cookie,并使用资源的表示形式发送相同的令牌(隐藏的输入等...)。其他所有内容都与上一个解决方案的结尾相同。这个解决方案处于RESTfulness的边缘。 (可以直到客户端不调用服务来修改cookie。如果cookie只是http,客户端不应该知道它,如果不是,那么客户端应该设置它。)你可以做更多如果您为每个表单添加不同的标记并为Cookie添加过期时间,则需要使用复杂的解决方案。您也可以使用表单发回过期时间,这样您就可以知道令牌验证失败的原因。

  • You can have a user secret (different by each user) in the resource state on you service. By building representations, you can generate a token (and expiration time) for each form. You can generate a hash from the actual token (and expiration time, method, url, etc...) and the user secret, and send that hash with the form as well. You keep the "user secret" in secret of course, so you never send that with the form. After that if your service gets a request, you can generate the hash from the request parameters and user secret again, and compare them. If the don't match, the request is invalid...

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

None of them will protect you if your REST client is javascript injectable, so you have to check all your user content against HTML entities, and remove all of them, or use TextNodes always instead of innerHTML. You have to protect yourself against SQL injection and HTTP header injection as well. Never use simple FTP to refresh your site. And so on... There are many ways to inject evil code into your site...

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

I almost forgot to mention, that GET requests are always for reading by the service and by the client either. By the service this is obvious, by the client setting any url in the browser must result a representation of a resource or multiple resources, it should never call a POST/PUT/DELETE method on a resource. For example GET http://my.client.com/resource/delete -> DELETE http://my.api.com/resource is a very-very bad solution. But this is very basic skill if you want to hinder CSRF.

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

#1


22  

There are a lot of answers here, and problems with quite a few of them.

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

Things you should NOT do:

你不应该做的事情:

  1. If you need to read the session token from JavaScript, you're doing something horribly wrong. Your session identifier cookie should ALWAYS have HTTPOnly set on it so its not available to scripts.

    如果您需要从JavaScript中读取会话令牌,那么您正在做一些可怕的错误。您的会话标识符cookie应始终在其上设置HTTPOnly,因此脚本不可用。

    This one protection makes it so that the impact of XSS is considerably reduced, since an attacker will no longer be able to get a logged in users session token, which for all intents and purposes are the equivalent of credentials in the application. You don't want one error to give keys to the kingdom.

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

  2. The session identifier should not be written to the contents of the page. This is for the same reasons you set HTTPOnly. This means that that your session token can not be your session id. They need to be different values.

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

Things you should do:

你应该做的事情:

  1. Follow OWASP's guidance:

    遵循OWASP的指导:

  2. Specifically, if this is a REST application you can require double-submission of CSRF tokens:

    具体来说,如果这是一个REST应用程序,您可以要求提交CSRF令牌的双重提交:

Simply create something cryptographically random, store it in ASCII Hex or Base64 encode, and add it as a cookie and to your forms when the server returns the page. On the server side make sure that the cookie value matches the form value. Voila, you've killed CSRF, avoided extra prompts for your users, and not opened yourself up to more vulnerabilities.

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

#2


9  

You definitely need some state on the server to authenticate/authorize. It need not be the http session though, you could store it in a distributed cache (like memcached) or a database.

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

If you use cookies for authentication, the easiest solution is to double-submit the cookie value. Before you submit the form, read the session id from the cookie, store it in a hidden field and then submit it. On the server side, confirm that the value in the request is the same as the session id (that you got from the cookie). Evil script from another domain will not be able to read the session id from the cookie, thus preventing CSRF.

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

This scheme uses a single identifier across the session.

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

If you want more protection, generate a unique id per-session per-form.

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

Also, DO NOT generate tokens in JS. Anybody can copy the code and run it from a different domain to attack your site.

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

#3


8  

Am I getting this right:

我是否正确:

  • You want protection against CSRF for users logged in via cookies.
  • 您希望通过cookie登录的用户免受CSRF的攻击。
  • And at the same time you want RESTful interface for Basic, OAuth and Digest authenticated requests from apps.
  • 同时,您需要来自应用程序的基本,OAuth和Digest经过身份验证的请求的RESTful接口。

So, why not check whether users is logged in via cookie and apply CSRF only then?

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

I'm not sure but is possible for another site to forge things like Basic auth or headers?

我不确定但是其他网站是否有可能伪造像Basic auth或者头文件这样的东西?

As far as I know , CSRF is all about cookies? RESTful auth doesn't happen with cookies.

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

#4


6  

The static form ID provides no protection at all; an attacker can fetch it himself. Remember, the attacker is not constrained to using JavaScript on the client; he can fetch the static form ID server-side.

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

I'm not sure I entirely understand the proposed defense; where does the GET /usersecret/john_doe come from? Is that part of the page JavaScript? Is that the literal proposed URL? If so, I'm assuming that username is not a secret, which means that evil.ru can recover user secrets if a browser or plugin bug allows cross-domain GET requests. Why not store the user secret in a cookie upon authentication rather than let anyone who can do cross-domain GETs retrieve it?

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

I would read "Robust Defenses for Cross-Site Forgery" really carefully before I implemented my own authentication system that I wanted to be resistant to CSRF. In fact, I would reconsider implementing my own authentication system at all.

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

#5


3  

There are a few methods in the CSRF Prevention Cheat Sheet that can be used by restful service. The most RESTful stateless CSRF mitigation is using the Origin or HTTP referer to make sure the requests originate from a domain you trust.

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

#6


0  

Is something wrong with this approach, despite the fact that it doesn't work without JavaScript?

这种方法有问题,尽管如果没有JavaScript它不起作用吗?

Your user secret is not a secret if you send it to the client. We usually use such secrets to generate hashes and send them with the form, and wait them back for comparison.

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

If you want to be RESTful, the request has to contain every information about how to process it. The ways you can do this:

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

  • Add a csrf token cookie with your REST client and send the same token in hidden input with your forms. If the service and the client are under different domains, you have to share the credentials. On the service you have to compare the 2 tokens, and if they are the same, the request is valid...

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

  • You can add the csrf token cookie with your REST service and send the same token with the representations of your resources (hidden inputs, etc...). Everything else is the same as the end of the previous solution. This solution is on the edge of RESTfulness. (It is okay until the client do not call the service to modify the cookie. If the cookie is http only, the client should not know about it, if it is not, then the client should set it.) You can do a more complex solution if you add different tokens to each forms and add expiration time to the cookies. You can send the expiration time back with the forms as well, so you will know the reason when a token validation fails.

    您可以使用REST服务添加csrf令牌cookie,并使用资源的表示形式发送相同的令牌(隐藏的输入等...)。其他所有内容都与上一个解决方案的结尾相同。这个解决方案处于RESTfulness的边缘。 (可以直到客户端不调用服务来修改cookie。如果cookie只是http,客户端不应该知道它,如果不是,那么客户端应该设置它。)你可以做更多如果您为每个表单添加不同的标记并为Cookie添加过期时间,则需要使用复杂的解决方案。您也可以使用表单发回过期时间,这样您就可以知道令牌验证失败的原因。

  • You can have a user secret (different by each user) in the resource state on you service. By building representations, you can generate a token (and expiration time) for each form. You can generate a hash from the actual token (and expiration time, method, url, etc...) and the user secret, and send that hash with the form as well. You keep the "user secret" in secret of course, so you never send that with the form. After that if your service gets a request, you can generate the hash from the request parameters and user secret again, and compare them. If the don't match, the request is invalid...

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

None of them will protect you if your REST client is javascript injectable, so you have to check all your user content against HTML entities, and remove all of them, or use TextNodes always instead of innerHTML. You have to protect yourself against SQL injection and HTTP header injection as well. Never use simple FTP to refresh your site. And so on... There are many ways to inject evil code into your site...

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

I almost forgot to mention, that GET requests are always for reading by the service and by the client either. By the service this is obvious, by the client setting any url in the browser must result a representation of a resource or multiple resources, it should never call a POST/PUT/DELETE method on a resource. For example GET http://my.client.com/resource/delete -> DELETE http://my.api.com/resource is a very-very bad solution. But this is very basic skill if you want to hinder CSRF.

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