会话模块不能保证存储在会话中的信息只能由创建该会话的用户查看。需要采取其他措施来保护会话的机密性,具体取决于与其关联的值。
需要评估会话中携带的数据的重要性,并可能部署进一步的保护措施;这通常会付出一定的代价,例如降低用户的便利性。例如,为了保护用户免受简单的社会工程战术的攻击,需要启用session.use_only_cookies。在这种情况下,必须在客户端无条件地启用 Cookie,否则会话将无法工作。
有几种方法可以将现有的会话 ID 泄漏给第三方。例如 JavaScript 注入、URL 中的会话 ID、数据包嗅探、对设备的物理访问等。泄漏的会话 ID 使第三方能够访问与特定 ID 关联的所有资源。首先,携带会话 ID 的 URL。如果存在指向外部站点或资源的链接,则包含会话 ID 的 URL 可能会存储在外部站点的引用日志中。其次,更积极的攻击者可能会监听网络流量。如果未加密,会话 ID 将以明文形式通过网络传输。解决方案是在服务器上实现 SSL/TLS 并使其对用户强制执行。应使用 HSTS 以提高安全性。
注意:即使是 HTTPS 也不能始终保护机密数据。例如,CRIME 和 Beast 漏洞可能使攻击者能够读取数据。此外,请注意,许多网络出于审计目的而使用 HTTPS MITM 代理。攻击者也可以设置这样的代理。
PHP 的会话管理器目前默认情况下是自适应的。自适应会话管理器存在额外风险。
当启用session.use_strict_mode并且会话保存处理程序支持它时,未初始化的会话 ID 将被拒绝,并将创建一个新的会话 ID。这可以防止迫使用户使用已知会话 ID 的攻击。攻击者可能会粘贴包含会话 ID 的链接或发送电子邮件。例如,如果启用了session.use_trans_sid,则http://example.com/page.php?PHPSESSID=123456789
受害者将使用攻击者提供的会话 ID 启动会话。session.use_strict_mode可以减轻此风险。
用户定义的保存处理程序还可以通过实现会话 ID 验证来支持严格的会话模式。所有用户定义的保存处理程序都应实现会话 ID 验证。
会话 ID Cookie 可以使用域、路径、httponly、secure 以及从 PHP 7.3 开始的 SameSite 属性进行设置。浏览器定义了优先级。通过使用优先级,攻击者可以设置会话 ID,该会话 ID 可以永久使用。session.use_only_cookies的使用不会解决此问题。session.use_strict_mode可以减轻此风险。使用session.use_strict_mode=On,未初始化的会话 ID 将被拒绝。
注意:即使session.use_strict_mode可以减轻自适应会话管理的风险,攻击者也可以迫使用户使用由攻击者创建的已初始化的会话 ID。例如,JavaScript 注入。此攻击可以通过本手册的建议来减轻。 通过遵循本手册,开发人员应启用session.use_strict_mode,使用基于时间戳的会话管理,并使用推荐的程序使用session_regenerate_id()重新生成会话 ID。如果开发人员遵循上述所有操作,攻击者生成的会话 ID 最终将被删除。 当访问过时的会话时,开发人员应保存用户的所有活动会话数据。因为此信息将与随后的调查相关。用户应强制从所有会话中注销,即要求他们重新进行身份验证。这可以防止攻击者滥用被盗会话。
访问过时的会话并不一定表示存在攻击。网络不稳定和/或立即删除活动会话将导致合法用户使用过时的会话。
从 PHP 7.1.0 开始,添加了session_create_id()。可以通过在会话 ID 前缀中添加用户 ID 来有效地操作此函数以访问用户的全部活动会话。在此设置中启用session.use_strict_mode至关重要。否则,恶意用户可以为其他用户设置恶意会话 ID。
注意:PHP 7.1.0 之前的用户应该使用CSPRNG,例如/dev/urandom,或random_bytes()和哈希函数来生成新的会话 ID。session_create_id()具有冲突检测功能,并根据会话的 INI 设置生成会话 ID。建议使用session_create_id()。
session.use_strict_mode是一种很好的缓解措施,但还不够。开发人员必须同样使用session_regenerate_id()来确保会话安全。
会话 ID 重新生成可以降低被盗会话 ID 的风险,因此必须定期调用session_regenerate_id()。例如,对于安全敏感的内容,每 15 分钟重新生成一次会话 ID。即使在会话 ID 被盗的情况下,合法用户和攻击者的会话都将过期。换句话说,用户或攻击者的访问将生成过时的会话访问错误。
当用户权限提升时(例如,身份验证后),必须重新生成会话 ID。session_regenerate_id()必须在将身份验证信息设置为$_SESSION之前调用。(session_regenerate_id()会自动保存当前会话数据,以便将时间戳/等保存到当前会话。)确保只有新会话包含已认证的标志。
开发人员不得依赖session.gc_maxlifetime来使会话 ID 过期。攻击者可能会定期访问受害者的会话 ID 以防止其过期并继续利用它,包括已认证的会话。
相反,开发人员必须实现基于时间戳的会话数据管理。
尽管会话管理器可以透明地管理时间戳,但此功能未实现。旧的会话数据必须保留到 GC。同时,开发人员必须确保自己删除了过时的会话数据。但是,开发人员不得立即删除活动会话数据。即session_regenerate_id(true);
和session_destroy()不得一起用于活动会话。这听起来可能自相矛盾,但这是一个强制性要求。
session_regenerate_id()默认情况下不会删除过时的会话。过时的已认证会话可能会保留以供使用。开发人员必须防止任何人使用过时的会话。他们必须自己使用时间戳禁止访问过时的会话数据。
突然删除活动会话会产生不良副作用。当存在与 Web 应用程序的并发连接和/或网络不稳定时,会话可能会消失。
通过突然删除活动会话,无法检测到潜在的恶意访问。
开发人员不应立即删除过时的会话,而应在$_SESSION中设置一个短期过期时间(时间戳),并自行禁止访问会话数据。
开发人员在调用session_regenerate_id()后,不能立即禁止访问旧会话数据。必须在稍后阶段禁止访问。例如,对于稳定的网络(如有线网络),可以在几秒钟后禁止;对于不稳定的网络(如手机或 Wi-Fi),可以在几分钟后禁止。
如果用户访问了过时的会话(已过期的会话),则应拒绝访问。还建议移除用户所有会话中的身份验证状态,因为这很可能代表一次攻击。
不恰当的使用session.use_only_cookies 和 session_regenerate_id() 可能会导致攻击者设置无法删除的 Cookie,从而引发个人拒绝服务攻击。在这种情况下,开发人员可以建议用户删除 Cookie,并告知他们可能受到安全问题的影响。攻击者可以通过易受攻击的 Web 应用程序、暴露的/恶意的浏览器插件、物理受损的设备等设置恶意 Cookie。
不要误解拒绝服务攻击的风险。session.use_strict_mode=On 对一般会话 ID 安全至关重要!建议所有网站启用session.use_strict_mode。
拒绝服务攻击仅在帐户受到攻击时才会发生。应用程序中的 JavaScript 注入漏洞是最常见的原因。
过时的会话数据必须无法访问并被删除。当前的会话模块对此处理不佳。
应尽快删除过时的会话数据。但是,不能立即删除活动会话。为了满足这些要求,开发人员必须自己实现基于时间戳的会话数据管理。
在 $_SESSION 中设置和管理过期时间戳。禁止访问过时的会话数据。当检测到访问过时的会话数据时,建议从用户的会话中删除所有身份验证状态并强制他们重新进行身份验证。访问过时的会话数据可能代表一次攻击。为了实现这一点,开发人员必须跟踪每个用户的全部活动会话。
注意: 访问过时的会话也可能是由于网络不稳定和/或并发访问网站造成的。例如,服务器尝试通过 Cookie 设置新的会话 ID,但由于连接丢失,Set-Cookie 数据包可能没有到达客户端。一个连接可能通过 session_regenerate_id() 发出新的会话 ID,但另一个并发连接可能尚未收到新的会话 ID。因此,开发人员必须在稍后阶段禁止访问过时的会话。即基于时间戳的会话管理是强制性的。
总之,会话数据不能使用 session_regenerate_id() 或 session_destroy() 销毁,而必须使用时间戳来控制对会话数据的访问。让 session_gc() 从会话数据存储中删除过时的数据。
默认情况下,会话数据会被锁定以避免竞争条件。锁定对于保持跨请求的会话数据一致性是强制性的。
但是,会话锁定可能会被攻击者滥用来执行拒绝服务攻击。为了减轻会话锁定导致的拒绝服务攻击的风险,请最大程度减少锁定。当不需要更新会话数据时,请使用只读会话。在 session_start() 中使用“read_and_close”选项。session_start(['read_and_close'=>1]);
通过使用 session_commit() 在更新 $_SESSION 后尽快关闭会话。
当前的会话模块在会话处于非活动状态时 *不会* 检测到 $_SESSION 的任何修改。在会话处于非活动状态时,不要修改 $_SESSION 是开发人员的责任。
开发人员应跟踪每个用户的全部活动会话。并通知他们活动会话的数量、来自哪个 IP(和地区)、活动了多长时间等。PHP 不会跟踪这些信息。开发人员应该这样做。
存在多种实现此功能的方法。一种可能的实现是设置一个数据库来跟踪所需的数据并存储任何相关信息。由于会话数据会被垃圾回收,因此开发人员必须注意垃圾回收的数据以保持活动会话数据库的一致性。
最简单的实现之一是“用户 ID 前缀的会话 ID”,并将所需的信息存储在 $_SESSION 中。许多数据库在选择字符串前缀方面具有良好的性能。开发人员可以使用 session_regenerate_id() 和 session_create_id() 来实现这一点。
永远不要使用机密数据作为前缀。如果用户 ID 是机密的,请考虑使用 hash_hmac()。
启用 session.use_strict_mode 对于此设置是强制性的。请确保已启用。否则,活动会话数据库可能会受到损害。
基于时间戳的会话管理对于检测对过时会话的访问是强制性的。当检测到对过时会话的访问时,应从用户的全部活动会话中删除身份验证标志。这可以防止攻击者继续利用被盗会话。
开发人员不能使用长生命周期的会话 ID 进行自动登录,因为这会增加会话被盗的风险。自动登录功能应由开发人员实现。
使用安全的单次哈希密钥作为自动登录密钥,使用 setcookie()。使用比 SHA-2 更强的安全哈希。例如,使用来自 random_bytes() 或 /dev/urandom 的随机数据的 SHA-256 或更高版本。
如果用户未经身份验证,请检查一次性自动登录密钥是否有效。如果有效,则对用户进行身份验证并设置新的安全一次性哈希密钥。自动登录密钥只能使用一次,即永远不要重用自动登录密钥,始终生成新的密钥。
自动登录密钥是长生命周期的身份验证密钥,应尽可能地对其进行保护。使用 path/httponly/secure/SameSite Cookie 属性来保护它。即除非必要,否则永远不要传输自动登录密钥。
开发人员必须实现禁用自动登录和删除不需要的自动登录密钥 Cookie 的功能。
会话和身份验证无法防止 CSRF 攻击。开发人员必须自己实现 CSRF 防护。
output_add_rewrite_var() 可用于 CSRF 防护。有关更多详细信息,请参阅手册页。
注意: 7.2.0 之前的 PHP 使用与 trans sid 相同的输出缓冲区和 INI 设置。因此,不建议在 7.2.0 之前的 PHP 版本中使用 output_add_rewrite_var()。
大多数 Web 应用程序框架都支持 CSRF 防护。有关更多详细信息,请参阅 Web 应用程序框架手册。
从 PHP 7.3 开始,可以为会话 Cookie 设置 SameSite 属性。这是一种可以缓解 CSRF 漏洞的额外措施。