存储跨子域 cookie 最安全的方法是什么

信息安全 饼干 会话管理 jwt dns 欺骗
2021-08-19 12:51:18

我在一家我们称之为“公司 x”的公司工作。这家公司有一个域companyx.com他们有一个管理 SSL 之类的云平台,但为了做到这一点,您需要使用该域。有3个应用程序app1app2并且app 3具有以下网址...

app1.companyx.com
app2.companyx.com
app3.companyx.com

这些都需要使用无状态身份验证,因为它们具有高度可扩展性。所有这些“共享”同一个会话 (JWT) 都使用仅 http 且安全的 cookie,但由于无法创建子域,它们都使用 companyx.com 域作为其 cookie。

安全团队反驳说我们正在向整个域“提供 cookie”。这对我来说似乎有点奇怪,因为该公司还控制着核心领域,但我也知道今天有很多关注内部和外部威胁。

那么有没有办法构造 cookie 以便 app1、app2 和 app3 可以访问但 app4 没有?有没有比只使用核心域更安全的方法?这真的有多大的威胁?

2个回答

来自RFC 6265

5.1.3.  Domain Matching

 A string domain-matches a given domain string if at least one of the
   following conditions hold:

   o  The domain string and the string are identical.  (Note that both
      the domain string and the string will have been canonicalized to
      lower case at this point.)

   o  All of the following conditions hold:

      *  The domain string is a suffix of the string.

      *  The last character of the string that is not included in the
         domain string is a %x2E (".") character.

      *  The string is a host name (i.e., not an IP address).

也就是说,如果 cookie 的 domain 参数中的域名不以句点开头,那么它不会让子域读取该 cookie。如果它确实以句点开头,那么所有子域都可以完全访问该 cookie 的值。

例如:

Set-Cookie: lang=en-US; Path=/; Domain=example.com

只能由 example.com 阅读。

Set-Cookie: lang=en-US; Path=/; Domain=.example.com

可以被 example.com 和任何子域读取,包括 foo.example.com、baz.bar.foo.example.com 和 baz.example.com。

Set-Cookie: lang=en-US; Path=/; Domain=foo.example.com

只能由 foo.example.com 读取。

Set-Cookie: lang=en-US; Path=/; Domain=.foo.example.com

可以被 foo.example.com 和任何子域读取,包括 baz.bar.foo.example.com,但不能被 example.com 或 baz.example.com 读取。

如果您想跨子域共享 cookie,但忽略其他子域,则应明确说明要读取哪些子域,为每个子域设置一个新 cookie,而不是使用通配符。

有几个选项。

一是简单的第三方cookies;当用户进行身份验证时,服务器会返回一个页面,该页面向用户有权访问的每个子域发出简单的请求——每个请求都有一个短暂的、一次性的、可验证的令牌。子域服务器验证令牌并在响应中设置一个会话 cookie,其范围仅限于它们的(子)域。这种方法的缺点包括发出一堆可能不必要的调用,需要一种方法来保护令牌,以及要求用户的浏览器接受第三方 cookie(大多数都接受,但不是全部)。

另一种方法是在 - 进行身份验证 - 并接收仅为 - 完全受信任的子域 (auth.companyx.com) 设置的会话 cookie。每当用户尝试访问另一个(子)域 (app1.companyx.com) 时,如果用户在该域上还没有 cookie,则该站点会返回一个脚本,该脚本会向 auth.companyx.com 发出经过身份验证的 CORS 请求. auth.companyx.com 上的服务器检查Origin: app1.companyx.com标头,验证用户在 auth.companyx.com 的令牌是否适用于授权使用 app1.companyx.com 的用户,并向浏览器返回一个令牌 - 可能是 JWT。浏览器将该令牌转发到 app1.companyx.com(通过同源 XHR),app1.companyx.com 验证令牌(通过简单地检查本地签名和有效性,或者通过对 auth 进行服务器到服务器调用.companyx.com 或查看一些共享数据库),并返回一个会话 cookie,其范围仅限于 app1.companyx.com。如果用户尝试访问 app4.companyx.com,则 auth.companyx.com 上的服务器会说用户无权访问该站点并且不为其提供令牌。这实际上是一个本地 SSO 系统。

上述两种情况都存在的一个问题是会话管理最终是以每个子域为基础的。如果用户在会话到期之前退出一个域,他们可能应该退出所有域,但这需要额外的工作来清除剩余的 cookie(或在服务器端使它们无效,这更安全但需要通知所有服务器并让它们存储有关哪些会话令牌有效/无效的状态)。

这里通常的方法只是将高度信任的域与所有其他域区分开来。如果 app1、app2 和 app3 受信任但 app4 不受信任,则 app4 不应与其他应用位于同一根域中。这基本上只是一般的良好安全实践。与受信任站点共享域根的不受信任站点可以执行诸如植入恶意 cookie 之类的操作,以尝试绕过 CSRF 保护或发起 XSS 攻击或针对受信任站点的某些操作。

或者,如果您信任 app4 但只是不希望它看到 app3 的会话令牌并认为这意味着用户也自动被授权访问 app4,那么这只是标准的访问控制情况。有很多方法可以处理这个问题,例如使用 JWT(或类似的签名身份验证令牌)并让每个站点自己处理授权,包括会话令牌中的“范围”(同样,它需要签名)以便每个服务器都可以查看令牌是否真实(未篡改)并查看身份验证服务器是否授予对特定应用程序的访问权限,等等。