为什么 auth0 建议不要将令牌存储在 localStorage 中?
Why is auth0 recommending not to store tokens in localStorage?
Auth0 提供了广泛的资源列表,描述了身份验证的最佳实践。其中有源源不断的建议不要使用 localStorage
作为存储 (JWT) 令牌的手段。
- https://auth0.com/docs/security/store-tokens#don-t-store-tokens-in-local-storage
- https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/HTML5_Security_Cheat_Sheet.md#local-storage
- https://github.com/auth0/docs/issues?utf8=%E2%9C%93&q=localstorage
我发现这些点有几个问题:
- 强烈假设 JWT 令牌是攻击者不能访问的敏感信息(通过 XSS)
- 从我的角度来看,访问令牌本身并没有扩大攻击范围。如果攻击者控制了受害者的浏览器,他们可以使用令牌从受影响的浏览器本身执行调用。
- 访问令牌的到期时间通常很短,并且可以固定在浏览器中,从而减少了在 XSS 上下文之外使用它们的机会。
- Auth0 建议使用他们的 SDK 中的
auth0.getTokenSilently()
来获取令牌,但据我所知,攻击者不能自己调用此方法(即注入另一个 sdk 脚本)没有任何理由,使用现有的客户端密钥,并从那里调用 getToken),从而以与存储在 localStorage
中几乎相同的方式获取令牌
- 我知道 XSS 在哪里无法访问令牌的唯一方法基本上是使用 httpOnly cookie,但是它自己创建了新的向量 (CSRF),仍然不能阻止攻击者调用 api 来自受影响的浏览器。
所以我完全同意OWASP的建议,不要在localStorage
中存储敏感数据,我永远不会想到存储信用卡号或密码甚至个人数据那里。但这只是因为这些东西会让攻击者扩大攻击范围(访问银行账户、尝试在其他应用程序中重用用户密码等)。但是我很难看到 accessTokens 是如何受此影响的。
虽然我不太了解 Auth0 的实现、功能和设计决策,但根据我对 OAuth2 和安全性的一般理解,我可以尝试将这些点联系起来。
单个建议本身并不能提供足够的安全性或所需的功能,但当与其他相关建议结合使用时,我们可以达到所需的安全性和行为级别。
让我们来看看你提出的观点。
From my perspective accessing the tokens itself doesn't extend the scope of attack. If the attacker has control over the victim's browser they can execute the calls, using the token, from the affected browser itself
localstorage
的问题是:
localStorage
和 sessionStorage
未在 sub-domains 之间共享。这是 SSO 功能的显示阻止程序(有一些解决方案使用 iframe
,但那些看起来更像是变通方法而不是好的解决方案。当响应 header X-Frame-Options 用于避免点击劫持时使用 iframe
进行攻击,使用 iframe
的任何解决方案都是不可能的)
XSS 可以将访问 and/or 刷新令牌发送到攻击者控制的远程服务器,从而允许攻击者冒充用户
注意:第 2 点中提到的漏洞可以通过使用 OWASP 中提到的需要 cookie 的 Sender Constrained Access Tokens approach where the client has to prove that they indeed own the token. Another alternative is the fingerprint approach 来缓解。然而,似乎 Auth0 没有实现这些。因此,避免localstorage
的建议是有道理的。
Auth0 recommends using auth0.getTokenSilently() from their SDK to obtain the token, but as far as I see, there shouldn't be any reason why attacker couldn't call this method themselves
正确。这就是为什么
- 我们首先需要遵循 OWASP XSS prevention guidelines 来降低 XSS 的风险。
- 此外,
getTokenSilently()
方法要求您在仪表板的 API 设置中启用 Allow Skipping User Consent
。虽然我没有看到这方面的具体指南,但我认为如果您将令牌存储在 cookie 中,则不需要启用此选项,从而消除了滥用该方法的任何可能性。
The only way that I know where XSS wouldn't be able to access the tokens is basically using httpOnly cookies, but that creates new vectors by itself (CSRF) and still wouldn't prevent attackers from calling the api from within the affected browser
没错。但是您可以通过以下一种方法或多种方法的组合来缓解这种情况:
- 使用
SameSite
cookie。请参阅文档 here。如果浏览器不支持 SameSite
cookie,请按照下面的另一种方法
- 状态变量(Auth0 使用它)- 客户端将生成并随每个请求传递一个加密的强随机随机数,服务器将回显该随机数及其响应,允许客户端验证该随机数。 Auth0 doc.
中对此进行了解释
- 使用具有加密强随机值的 CSRF cookie,以便每个 AJAX 请求读取 cookie 值并在自定义 HTTP header 中添加 cookie 值(GET 和 HEAD 请求除外,它们是不应该做任何状态修改)。由于 CSRF 由于同源策略而无法读取任何内容,并且它基于利用 POST、PUT 和 DELETE 等不安全的 HTTP 方法,因此此 CSRF cookie 将减轻 CSRF 风险。所有现代 SPA 框架都使用这种使用 CSRF cookie 的方法。提到了 Angular 方法 here
- 始终检查引荐来源 header 并仅在引荐来源是受信任的域时才接受请求。如果 referer header 不存在或 non-whitelisted 域,则简单地拒绝该请求。使用 SSL/TLS 时通常会出现引荐来源网址。着陆页(主要是信息性的,不包含登录表单或任何安全内容可能有点宽松,并允许缺少引荐来源网址的请求header
- 应在服务器中阻止 TRACE HTTP 方法,因为它可用于读取 httpOnly cookie
- 此外,将 header
Strict-Transport-Security: max-age=<expire-time>; includeSubDomains
设置为仅允许安全连接以防止任何 man-in-the-middle 覆盖来自 sub-domain 的 CSRF cookie
Auth0 提供了广泛的资源列表,描述了身份验证的最佳实践。其中有源源不断的建议不要使用 localStorage
作为存储 (JWT) 令牌的手段。
- https://auth0.com/docs/security/store-tokens#don-t-store-tokens-in-local-storage
- https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/HTML5_Security_Cheat_Sheet.md#local-storage
- https://github.com/auth0/docs/issues?utf8=%E2%9C%93&q=localstorage
我发现这些点有几个问题:
- 强烈假设 JWT 令牌是攻击者不能访问的敏感信息(通过 XSS)
- 从我的角度来看,访问令牌本身并没有扩大攻击范围。如果攻击者控制了受害者的浏览器,他们可以使用令牌从受影响的浏览器本身执行调用。
- 访问令牌的到期时间通常很短,并且可以固定在浏览器中,从而减少了在 XSS 上下文之外使用它们的机会。
- Auth0 建议使用他们的 SDK 中的
auth0.getTokenSilently()
来获取令牌,但据我所知,攻击者不能自己调用此方法(即注入另一个 sdk 脚本)没有任何理由,使用现有的客户端密钥,并从那里调用 getToken),从而以与存储在localStorage
中几乎相同的方式获取令牌
- 我知道 XSS 在哪里无法访问令牌的唯一方法基本上是使用 httpOnly cookie,但是它自己创建了新的向量 (CSRF),仍然不能阻止攻击者调用 api 来自受影响的浏览器。
所以我完全同意OWASP的建议,不要在localStorage
中存储敏感数据,我永远不会想到存储信用卡号或密码甚至个人数据那里。但这只是因为这些东西会让攻击者扩大攻击范围(访问银行账户、尝试在其他应用程序中重用用户密码等)。但是我很难看到 accessTokens 是如何受此影响的。
虽然我不太了解 Auth0 的实现、功能和设计决策,但根据我对 OAuth2 和安全性的一般理解,我可以尝试将这些点联系起来。
单个建议本身并不能提供足够的安全性或所需的功能,但当与其他相关建议结合使用时,我们可以达到所需的安全性和行为级别。
让我们来看看你提出的观点。
From my perspective accessing the tokens itself doesn't extend the scope of attack. If the attacker has control over the victim's browser they can execute the calls, using the token, from the affected browser itself
localstorage
的问题是:
localStorage
和sessionStorage
未在 sub-domains 之间共享。这是 SSO 功能的显示阻止程序(有一些解决方案使用iframe
,但那些看起来更像是变通方法而不是好的解决方案。当响应 header X-Frame-Options 用于避免点击劫持时使用iframe
进行攻击,使用iframe
的任何解决方案都是不可能的)XSS 可以将访问 and/or 刷新令牌发送到攻击者控制的远程服务器,从而允许攻击者冒充用户
注意:第 2 点中提到的漏洞可以通过使用 OWASP 中提到的需要 cookie 的 Sender Constrained Access Tokens approach where the client has to prove that they indeed own the token. Another alternative is the fingerprint approach 来缓解。然而,似乎 Auth0 没有实现这些。因此,避免localstorage
的建议是有道理的。
Auth0 recommends using auth0.getTokenSilently() from their SDK to obtain the token, but as far as I see, there shouldn't be any reason why attacker couldn't call this method themselves
正确。这就是为什么
- 我们首先需要遵循 OWASP XSS prevention guidelines 来降低 XSS 的风险。
- 此外,
getTokenSilently()
方法要求您在仪表板的 API 设置中启用Allow Skipping User Consent
。虽然我没有看到这方面的具体指南,但我认为如果您将令牌存储在 cookie 中,则不需要启用此选项,从而消除了滥用该方法的任何可能性。
The only way that I know where XSS wouldn't be able to access the tokens is basically using httpOnly cookies, but that creates new vectors by itself (CSRF) and still wouldn't prevent attackers from calling the api from within the affected browser
没错。但是您可以通过以下一种方法或多种方法的组合来缓解这种情况:
- 使用
SameSite
cookie。请参阅文档 here。如果浏览器不支持SameSite
cookie,请按照下面的另一种方法 - 状态变量(Auth0 使用它)- 客户端将生成并随每个请求传递一个加密的强随机随机数,服务器将回显该随机数及其响应,允许客户端验证该随机数。 Auth0 doc. 中对此进行了解释
- 使用具有加密强随机值的 CSRF cookie,以便每个 AJAX 请求读取 cookie 值并在自定义 HTTP header 中添加 cookie 值(GET 和 HEAD 请求除外,它们是不应该做任何状态修改)。由于 CSRF 由于同源策略而无法读取任何内容,并且它基于利用 POST、PUT 和 DELETE 等不安全的 HTTP 方法,因此此 CSRF cookie 将减轻 CSRF 风险。所有现代 SPA 框架都使用这种使用 CSRF cookie 的方法。提到了 Angular 方法 here
- 始终检查引荐来源 header 并仅在引荐来源是受信任的域时才接受请求。如果 referer header 不存在或 non-whitelisted 域,则简单地拒绝该请求。使用 SSL/TLS 时通常会出现引荐来源网址。着陆页(主要是信息性的,不包含登录表单或任何安全内容可能有点宽松,并允许缺少引荐来源网址的请求header
- 应在服务器中阻止 TRACE HTTP 方法,因为它可用于读取 httpOnly cookie
- 此外,将 header
Strict-Transport-Security: max-age=<expire-time>; includeSubDomains
设置为仅允许安全连接以防止任何 man-in-the-middle 覆盖来自 sub-domain 的 CSRF cookie