您需要加密会话数据吗?

信息安全 验证 php 会话管理 会话固定
2021-08-26 19:19:12

我在 PHP 中遇到了一个会话管理类,它加密会话存储文件夹(即/tmp)中的会话数据,并且可以稍后在您的脚本中使用密钥进行解密。我想知道它是否真的需要?如果您已经像这样(简化)示例进行了一些会话劫持预防:

session_start();

if (isset($_SESSION['fingerprint']))
    if ($_SESSION['fingerprint'] != md5($_SERVER['HTTP_USER_AGENT'].'SECRETSALT'))          
        exit; // prompt for password
else
    $_SESSION['fingerprint'] = md5($_SERVER['HTTP_USER_AGENT'].'SECRETSALT');

你还需要加密你的会话数据吗?或者仅当您通过会话存储敏感信息(例如,个人信息、抄送号码、凭据)时才需要加密?

此外,如果您以如下简单的方式验证用户是否已登录:

// 登录.php

if ($_POST['password'] == $db_password)
{
    $_SESSION['logged_in'] = true;
    redirect_to_protected_area();
}
else
    // show login again

// protected_area.php

if (!isset($_SESSION['logged_in']) OR !$_SESSION['logged_in'])
    exit; // prompt for password
else
    // show protected area

如果会话数据未加密并且黑客一目了然地看到数据(即指纹的 md5 哈希值和logged_in = true. 他真的可以自己登录还是必须先“破解”md5?

注意:md5 用于简化示例,现实生活中使用了更好的哈希算法。

4个回答

用 PHP 加密你的会话数据有点像锁上你的车,把你的钥匙放在车顶上。考虑适用的威胁场景:

  1. 攻击者获得对服务器的一些远程访问权限并尝试读取会话数据文件。
  2. 攻击者从数据中心窃取服务器的整个硬盘。这可能是通过物理盗窃或通过访问虚拟化环境中的虚拟硬盘文件。

在第一种情况下,攻击者需要获得对服务器的访问权限,然后将权限升级到 root 或冒充拥有会话数据目录的用户(例如,www-data对于 Apache,nobody对于 nginx)。这些目录应用了访问控制(通常是0600权限位),以防止所有者以外的用户访问数据。

如果攻击者已经可以访问服务器并且可以冒充运行 Web 服务器守护程序的用户,那么他们可以按照定义访问 Web 服务器进程可以访问的所有数据,这意味着他们按照定义可以访问Web 服务器或在其下运行的代码可能正在使用的任何加密密钥(HSM 除外,尽管在这里使用它不是一个合理的假设)。

这意味着如果攻击者可以读取会话数据文件,他们也可以读取加密密钥,这意味着他们可以解密会话数据。加密最终充其量只是混淆。

在第二种情况下,同样的问题也适用:如果密钥与会话数据一起在硬盘上,攻击者可以简单地读取密钥并解密会话数据。解决这个问题的方法是对会话数据进行加密,使密钥不存储在磁盘上,正确的方法是使用全盘加密(FDE)并在启动时使用 out-of 手动输入密码- 频带管理(例如 iLO、DRAC、远程 KVM 等),以便以这样一种方式对整个硬盘进行加密,如果磁盘被盗,攻击者将无法访问磁盘上的任何数据。

然而,即使在这种情况下,FDE 也可能在保护会话数据方面没有太大的安全收益。原因是,在大多数平台和配置上,会话数据存储在/tmptmpfs 卷支持的中。tmpfs 的全部意义在于它是易失性的(即数据在重新启动时丢失)并且在大多数情况下内容存储在 RAM 中。很长一段时间未触及的数据可能会被交换到磁盘,这就是 FDE所做的提供了保护会话数据的好处,但很可能任何旧会话都将在此发生之前过期。话虽如此,我仍然建议在所有系统上使用 FDE 作为良好安全实践的一部分,因为它可以防止其他类型的数据被盗(例如基础设施凭证、SSL 证书私钥等)

在 PHP 中加密会话数据有意义的唯一情况会话数据存储在数据库或键值存储中。在这种情况下,您应该预期会话数据可能是非易失性的(即存储在磁盘上),并且可能存在攻击者破坏数据库(例如进入被忽视的 phpMyAdmin 实例)而不是文件系统的情况。在这种情况下,密钥将位于文件系统上,攻击者无法访问。然而,这是极其罕见的情况。

我经常听到的另一个论点是,出于合规性原因,某些数据必须加密,例如 PCI-DSS 下的 PAN 数据。这里的解决方案取决于确切的系统架构,但一般来说,您可以通过根本不将数据存储在会话数据中,通过使用外部代理/HSM 加密/解密数据以隔离密钥,或者简单地解决此问题如果您只需要对静态数据进行离线加密,则可以使用 FDE 。

虽然出于上面讨论的原因我一般不推荐它,但如果您出于某种原因在 PHP 中设置加密会话数据,实际上很有可能在 PHP 中执行此操作而无需修改$_SESSION. 您可以使用SessionHandler 类来覆盖会话的正常处理。该文档提供了有关如何加密会话数据的简单说明。

但是,您需要一种在会话中创建和存储密钥的方法。显然,这不能通过会话管理器本身来完成,否则您将使用密文分发密钥。我的建议是使用内存缓存,例如APCmemcache,其中缓存中的键名是会话 ID。

如果您使用的是合理的 Web 框架(设计还算不错的),则不需要加密会话数据。这确实应该是框架的责任。

但是,如果您使用的是 PHP,那么您并没有使用合理的 Web 框架。PHP 在很多方面都是安全问题的孩子。其中一种方法是,默认情况下,它将会话数据存储在/tmp. 在某些共享主机服务上,/tmp可能会在用户之间共享。因此,PHP 将会话数据存储在其他人可以在未经授权的情况下查看它的位置。这是一个糟糕的设计缺陷——但是,嘿,PHP 充满了糟糕的设计缺陷,这就是 PHP 的生活。

因此,如果您在共享托管服务(其他人可以访问)上使用 PHP /tmp,是的,您需要加密会话数据。一种方法是使用session_set_save_handler()挂钩来加密会话数据确保使用具有良好密钥管理的强加密(使用经过身份验证的加密,避免常见的加密陷阱)(看在上帝的份上,不要将加密密钥存储在/tmp共享托管服务的其他客户可以看到的任何其他地方)。

或者,将您的 PHP 应用程序托管在专用机器上(例如,专用虚拟机;VPS;专用物理机器),并且不要让您不信任的任何人登录该机器。或者,如果您确信自己没有在会话存储中存储任何敏感信息,则不需要加密会话数据(但这在维护时很脆弱,因为其他人很容易添加功能将来会在没有意识到安全后果的情况下将一些敏感信息添加到会话存储中)。

但实际上,更好的答案是:使用认真的、精心设计的 Web 框架。朋友不要让朋友使用裸 PHP 来解决安全关键问题。例如,请参阅Jeff Atwood 关于此主题的博文

如果密钥存储在服务器端并没有多大意义 - 但密钥可能来自浏览器/用户。即使对服务器/源代码有一些访问权限,在这种情况下(如果它通过 SSL)访问密钥更加困难。

会话数据可以存储在文件系统/数据库中 - 并因此保留在备份中。此外,在共享系统上,根据其配置方式,这为会话数据提供了深度安全性——一个用户帐户映射到一个站点,很容易限制 ssh / ftp 访问,但限制 PHP 访问特定目录树有点复杂(有一些方法可以绕过 open_base_dir);对于低端托管,将每个网络服务器作为每个站点的单独 uid / 单独 FPM 组运行只是没有经济意义。

虽然没有人应该在这样的系统上存储信用卡详细信息 - 如果您只想要一个简单的前端来偶尔调用其他服务 - 例如通过 ssh 轮询另一台服务器的可用性/性能、发送 SMS 或访问邮箱?在这些情况下,您很可能会在会话中临时保留辅助身份验证令牌/加密密钥/远程会话标识符。

虽然将 PHP src 文件的写入权限限制为 webserver uid 之外的一小部分用户是微不足道的但 webserver uid 必须能够写入会话数据。一旦用户通过身份验证,授权通常基于存储在会话中的经过身份验证的用户 ID - 因此这可能是防止特权升级的解决方案的一部分(需要帖子中未提及的其他组件)。

根据密钥的管理方式,在某些边缘情况下,它将有助于安全性。

使用 cookie 仅存储会话 ID 会更简单。然后服务器必须记住哪个用户属于哪个会话,以及每个用户拥有哪些权限。这种设计的缺点是它降低了负载平衡的效率,因为会话数据必须在所有应用程序实例之间进行通信。

更好的设计是让登录节点执行身份验证并在数据库中查找用户权限。然后,此数据作为加密签名令牌返回给客户端,该令牌可以存储在 cookie 或 URL 中。需要签署此令牌,因为加密不会禁止篡改令牌。因此,每个服务在对签名进行操作之前验证签名是至关重要的。这实际上是JSON Web Tokens之间的核心思想