首页 > 解决方案 > Django 签名 cookie 会话存储、重放攻击和 SESSION_COOKIE_AGE

问题描述

根据Django 文档,签名的 cookie 会话存储容易受到重放攻击:

另请注意,虽然 MAC 可以保证数据的真实性(它是由您的站点生成的,而不是其他人生成的)和数据的完整性(它都在那里并且正确),但它不能保证新鲜度,即您将被退回您发送给客户的最后一件事。这意味着对于会话数据的某些用途,cookie 后端可能会使您面临重放攻击。与保留每个会话的服务器端记录并在用户注销时使其失效的其他会话后端不同,基于 cookie 的会话在用户注销时不会失效。因此,如果攻击者窃取了用户的 cookie,即使用户注销,他们也可以使用该 cookie 以该用户身份登录。只有当 Cookie 早于您的 SESSION_COOKIE_AGE 时,它们才会被检测为“陈旧”。

这是否意味着:

  1. 我们依靠 cookie 的客户端过期来确保会话数据被销毁(因此,如果在浏览器删除 cookie 之前捕获 cookie 内容,仍然可能发生重放攻击),或者
  2. SESSION_COOKIE_AGE服务器检测数据的陈旧性(与它认为陈旧的数据进行比较并明确拒绝。

从技术上讲,Django 能够确定会话的“旧”程度似乎在技术上是可能的,而无需在服务器端持久保存此数据,但文档似乎不清楚这是否正在完成,或者 Django 是否依赖/信任用户的浏览器杀死旧的 cookie(因此如果数据在过期之前被捕获,会话仍然可以重播)。

标签: djangosessioncookies

解决方案


Django 通过SessionMiddleware确实将HTTP cookie 过期时间设置为SESSION_COOKIE_AGE. 这告诉浏览器cookie什么时候应该被认为“太旧”和过期。这是很好的家政服务。此外,如果浏览器检测到不应再使用 cookie,它将不会使用它,这样 Django 就不会花时间检查 cookie can't be used Anyway

然而,Django 并不依赖浏览器来确保过期的 cookie 永远不会被再次使用。流氓代理可以获取 cookie 的副本,然后在过期后重新使用它。Django 通过将 cookie 发送到格式如下的浏览器来防止这种情况:

<payload>:<creation stamp>:<signature>

冒号是出现在数据中的分隔符。<payload>是实际的会话数据。<creation stamp>是创建cookie时添加的时间戳,<signature>是对payload和创建时间戳进行签名的MAC签名。

您可以在 中查看相关代码django.core.signing。文件顶部的注释为您提供了使用Signer该类的基本签名方案。这个类不知道时间戳。但是,在签署 cookie 时,Django 使用TimestampSigner,它知道时间戳并生成我上面显示的格式。

TimestampSigner 取消签名cookie时,它​​会检查时间戳,如果太旧则引发异常:

def unsign(self, value, max_age=None):
    """
    Retrieve original value and check it wasn't signed more
    than max_age seconds ago.
    """
    result = super().unsign(value)
    value, timestamp = result.rsplit(self.sep, 1)
    timestamp = baseconv.base62.decode(timestamp)
    if max_age is not None:
        if isinstance(max_age, datetime.timedelta):
            max_age = max_age.total_seconds()
        # Check timestamp is not older than max_age
        age = time.time() - timestamp
        if age > max_age:
            raise SignatureExpired(
                'Signature age %s > %s seconds' % (age, max_age))
    return value

推荐阅读