PHP 会话固定攻击 / 劫持

150

我正在尝试更深入地了解 PHP 会话固定 和劫持问题,以及如何防止这些问题。我一直在阅读 Chris Shiflett 网站上的以下两篇文章:

然而,我不确定我是否正确理解了这些内容。

为了帮助防止会话固定,仅调用 session_regenerate_id(true); 是否就足够了?我认为是这样理解的。

他还谈到使用通过 $_GET 传递的令牌来防止会话劫持。这应该怎么做?我猜测当有人成功登录时,您会生成他们的令牌并将其存储在会话变量中,然后在每个页面上,您将比较该会话变量与 $_GET 变量的值?

这个令牌是需要每个会话只更改一次还是每次页面加载都需要更改?

此外,是否有一种良好的方法可以在不必通过 URL 传递值的情况下防止劫持?这将更容易些。


也许你可以添加链接到你找到这些建议的页面。 - Gumbo
5个回答

229
好的,有两个独立但相关的问题,每个问题都有不同的处理方式。
会话固定
这是攻击者明确设置用户会话标识符的地方。通常在PHP中,通过给他们一个类似于http://www.example.com/index...?session_name=sessionid的URL来实现。一旦攻击者将URL提供给客户端,攻击就与会话劫持攻击相同。
有几种方法可以防止会话固定(请全部采用):
在你的php.ini文件中设置session.use_trans_sid = 0。这将告诉PHP不在URL中包含标识符,并且不读取URL中的标识符。
在你的php.ini文件中设置session.use_only_cookies = 1。这将告诉PHP永远不要使用带有会话标识符的URL。
每当会话状态发生变化时重新生成会话ID。这意味着以下任何一种情况:
- 用户认证 - 在会话中存储敏感信息 - 更改会话的任何内容 - 等等...

会话劫持

这是攻击者获取会话标识符并能够发送请求,仿佛他们是该用户的地方。这意味着由于攻击者拥有标识符,他们在服务器方面几乎无法与有效用户区分开来。
你无法直接防止会话劫持。但是,你可以采取一些措施,使其变得非常困难和难以使用。
  • php.ini中使用一个强大的会话哈希标识符:session.hash_function。如果PHP版本小于5.3,则将其设置为session.hash_function = 1以使用SHA1。如果PHP版本大于等于5.3,则将其设置为session.hash_function = sha256session.hash_function = sha512

  • php.ini中发送一个强大的哈希:session.hash_bits_per_character。将其设置为session.hash_bits_per_character = 5。虽然这并不会使破解变得更加困难,但在攻击者尝试猜测会话标识符时会有所不同。会话ID会更短,但使用更多的字符。

  • php.ini文件中使用session.entropy_filesession.entropy_length设置额外的熵。将前者设置为session.entropy_file = /dev/urandom,将后者设置为从熵文件中读取的字节数,例如session.entropy_length = 256

  • 将会话的名称从默认的PHPSESSID更改为其他名称。在调用session_start之前,通过将自己的标识符名称作为第一个参数调用session_name()来实现。

  • 如果你真的很偏执,你也可以定期更改会话名称,但要注意,如果你更改了会话名称(例如,如果你将其与时间相关联),所有会话将自动失效。但根据你的用例,这可能是一个选择...

  • 经常更换会话标识符。我不会在每个请求中都这样做(除非你真的需要那种级别的安全性),而是在随机的时间间隔内更换。你希望经常更改这个,因为如果攻击者劫持了一个会话,你不希望他们能够长时间使用它。

  • 在会话中包含来自$_SERVER['HTTP_USER_AGENT']的用户代理。基本上,当会话开始时,将其存储在类似$_SESSION['user_agent']的变量中。然后,在每个后续请求中检查它是否匹配。请注意,这可以被伪造,所以它并不是100%可靠,但比没有好。

  • 在会话中包含来自$_SERVER['REMOTE_ADDR']的用户IP地址。基本上,当会话开始时,将其存储在类似$_SESSION['remote_ip']的变量中。这可能会对一些使用多个IP地址的ISP(例如AOL曾经使用的方式)造成问题。但如果你使用它,它将更加安全。攻击者伪造IP地址的唯一方法是在真实用户和你之间的某个网络上进行入侵。如果他们入侵了网络,他们可以做比劫持更糟糕的事情(例如中间人攻击等)。

  • 在会话和浏览器端包含一个令牌,你可以经常递增并进行比较。基本上,对于每个请求,在服务器端执行$_SESSION['counter']++。在浏览器端使用JS执行类似的操作(使用本地存储)。然后,当你发送请求时,只需获取令牌的一个随机数,并验证服务器上的随机数是否相同。通过这样做,你应该能够检测到劫持的会话,因为攻击者将没有确切的计数器,或者如果他们确实有,你将有两个系统传输相同的计数,并且可以判断其中一个是伪造的。这种方法不适用于所有应用程序,但是是解决问题的一种方式。

关于两者的说明

会话固定和劫持的区别仅在于会话标识符的泄露方式。在固定中,攻击者事先知道标识符的值。而在劫持中,标识符要么被猜测,要么从用户那里窃取。一旦标识符被泄露,两者的影响是相同的。

会话标识符重新生成

每当使用session_regenerate_id重新生成会话标识符时,旧的会话应该被删除。核心会话处理程序会自动完成此操作。然而,一些使用session_set_save_handler()的自定义会话处理程序没有执行此操作,因此容易受到旧会话标识符的攻击。如果您使用自定义会话处理程序,请确保跟踪您打开的标识符,并且如果它与保存的标识符不同,您需要显式地删除(或更改)旧标识符上的标识符。

使用默认的会话处理程序,只需调用session_regenerate_id(true)即可。这将为您删除旧的会话信息。旧的ID不再有效,如果攻击者(或其他人)尝试使用它,将会创建一个新的会话。但是,对于自定义的会话处理程序要小心……
销毁会话
如果要销毁会话(例如在注销时),请确保彻底销毁它。这包括取消设置cookie。使用session_destroy
function destroySession() {
    $params = session_get_cookie_params();
    setcookie(session_name(), '', time() - 42000,
        $params["path"], $params["domain"],
        $params["secure"], $params["httponly"]
    );
    session_destroy();
}

4
使用每个字符5位而不是4位并不会以任何方式改变“强度”(无论在这种情况下“强度”指什么)。但是尽管你的观点通常是可取的,它们缺少一些重要的细节。例如,与旧会话ID相关联的会话会发生什么,或者当旧会话ID失效后如何处理带有旧会话ID的会话。 - Gumbo
2
@battal:不,这就是关键。session_regenerate_id 不会使与旧 ID 关联的会话失效;只有在将 delete_old_session 参数设置为 true 时,会话才会被销毁。但如果攻击者发起了此 ID 重建,该怎么办? - Gumbo
6
我不同意每次更改会话变量时都重新生成会话,应该只在登录/注销时重新生成。检查用户代理是无意义的,并且检查REMOTE_ADDR存在问题。我想要补充一件事是 session.entropy_file = /dev/urandom。PHP内部的熵生成被证明非常薄弱,而/dev/random或/dev/uranom提供的熵池是您在没有硬件随机数生成器的Web服务器上可以得到的最好的选择。 - rook
4
你需要添加 session.cookie_httponlysession.cookie_secure。第一个有助于防止 XSS 攻击(但不是完美的)。第二个是阻止 OWASP A9 攻击的最佳方法。 - rook
4
不理解这样一个很棒的答案,但是缺少最重要的部分:使用SSL/HTTPS。计数器增量是在多个请求快速相继发生时出现问题的源头,例如用户刷新页面两次或连续点击提交按钮。IP地址解决方案现在对于所有移动用户和不断变化的IP都是一个问题。您可以查看IP的第一组,但仍然会有麻烦。最好的方法是从一开始就防止会话ID被发现,这就是使用SSL/HTTPS。 - Sanne
显示剩余19条评论

37

两种会话攻击的目标相同:获取另一个用户的合法会话。但攻击向量是不同的:

  • 会话固定攻击中,攻击者已经可以访问有效的会话,并试图强制受害者使用这个特定的会话。

  • 会话劫持攻击中,攻击者试图获取受害者会话的ID以使用他/她的会话。

在这两种攻击中,会话ID是这些攻击所关注的敏感数据。因此,对于读取访问(会话劫持)和写入访问(会话固定),需要保护会话ID。

使用HTTPS来保护敏感数据的一般规则也适用于本案例。此外,您应该执行以下操作:

为了防止会话固定攻击,请确保:

为了防止会话劫持攻击,请确保:

为了防止两种会话攻击,请确保:

  • 只接受您的应用程序启动的会话。您可以通过在客户端特定信息的初始化时对会话进行指纹识别来实现此操作。您可以使用User-Agent ID,但不要使用远程IP地址或任何可能在请求之间更改的其他信息。
  • 在身份验证尝试(仅在成功时使用true)或权限更改后使用session_regenerate_id(true)更改会话ID并销毁旧会话。(如果您想保留与旧ID关联的会话,则请确保在重新生成ID之前使用session_write_close存储$_SESSION的任何更改;否则,只有具有新ID的会话将受到这些更改的影响。)
  • 使用适当的会话过期实现(请参见如何在30分钟后使PHP会话过期?)。

很棒的文章,尤其是最后一节。 - Mattis

7
您所提到的令牌是“nonce”——一次性使用的数字。它们不一定只能使用一次,但使用时间越长,nonce 被捕获并用于劫持会话的可能性就越高。
另一个 nonce 的缺点是很难建立一个使用它们并允许在同一个表单上打开多个并行窗口的系统。例如,用户在论坛上打开两个窗口,并开始处理两篇文章。
window 'A' loads first and gets nonce 'P'
window 'B' loads second and gets nonce 'Q'

如果您无法跟踪多个窗口,那么您只能存储一个nonce-即B/Q窗口的nonce。当用户从A窗口提交其帖子并传递nonce“P”时,系统将拒绝该帖子,因为P != Q


那么这与会话固定有什么关系呢? - rook
2
他的观点很有道理,特别是在同时使用多个AJAX请求的领域中。 - DanielG

2

我没有阅读Shiflett的文章,但我认为你可能误解了一些内容。

默认情况下,当客户端不接受cookie时,PHP会在URL中传递会话令牌。否则,在最常见的情况下,会话令牌将存储为cookie。

这意味着,如果您将会话令牌放入URL中,PHP将识别它并尝试随后使用它。会话固定发生在某人创建会话,然后通过打开包含会话令牌的URL来欺骗另一个用户共享同一会话。如果用户以某种方式进行身份验证,则恶意用户知道已经经过身份验证的用户的会话令牌,后者可能具有不同的特权。

正如Shiflett所解释的那样,通常的做法是在用户特权更改时重新生成不同的令牌。


请注意,此外,请务必销毁任何先前打开的会话,因为它们仍将在现有用户权限下有效。 - corrodedmonkee

0

你可以通过在登录时重新生成会话ID来防止会话固定。这样,攻击者将不知道新认证会话的cookie值。另一种完全停止问题的方法是在运行时配置中设置session.use_only_cookies=True。攻击者无法在另一个域的上下文中设置cookie的值。会话固定依赖于将cookie值作为GET或POST发送。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接