防止会话劫持

79

如何防止多个客户端使用相同的会话ID?我之所以提出这个问题,是因为我想添加一个额外的安全层,以防止我的网站遭受会话劫持。如果黑客某种方式得知了另一个用户的会话ID,并用该SID发出请求,我该如何检测到不同的客户端共享单个SID,并拒绝劫持尝试?

编辑

在经过仔细考虑后,我接受了Gumbo的答案,因为我意识到由于无状态HTTP协议的限制,我所要求的是不可能的。我忘记了HTTP最基本的原则,现在我认为这个问题似乎有点琐碎。

让我解释一下我的意思:

当用户A在example.com上登录后,他会获得一个随机的会话ID,为了简单起见,我们称之为“abc123”。这个会话ID将作为cookie存储在客户端,并与服务器端的会话进行验证,以确保已登录的用户在从一个网页移动到另一个网页时仍然保持登录状态。当然,如果HTTP不是无状态的,则不需要存在这个cookie。因此,如果用户B窃取了用户A的SID,并在其计算机上创建了一个值为“abc123”的cookie,则他将成功劫持用户A的会话,但服务器根本无法合法地识别用户B的请求与用户A的请求有任何区别,因此服务器没有拒绝任何请求的理由。即使我们列出了服务器上已经活动的会话,并尝试查看是否有人正在访问已经活动的会话,我们如何确定访问会话的是另一个未经许可的用户而不是已使用会话ID登录的同一用户,只是想用它进行另一个请求(即导航到另一个网页)。我们做不到。检查用户代理?可以欺骗——但仍然可以作为深度防御措施。IP地址?可能会因合法原因而更改——但与其根本不检查IP地址,我建议检查IP的前两个八位字节,因为即使是一个在数据计划网络上经常更改IP的用户,出于完全合法的原因,他们通常只会更改其IP地址的最后两个八位字节。

总之,无状态的HTTP使我们永远无法完全保护我们的网站免受会话劫持,但是好的实践(例如Gumbo提供的实践)足以预防绝大多数会话攻击。因此,试图通过拒绝相同SID的多个请求来保护会话免受劫持是荒唐的,并且将打败会话的全部目的。


好的!检查IP地址的前两个八位并不是很有效。尽管整个IP地址不同,但使用相同互联网服务的不同人可能具有相同的前两个八位。 - WatsMyName
前两个八位组也可能合法地改变 - 例如在一个大型组织中,存在通过不同ISP的多个互联网网关。 - shonky linux user
当手机从Wi-Fi切换到蜂窝数据(即离开家或办公室)时,几乎可以肯定前两个IP地址段会发生变化。 - undefined
9个回答

96

很遗憾,没有一种可靠的方法可以明确地区分攻击者发起的请求和真实请求。因为绝大多数反制措施所检查的属性,例如 IP 地址或用户代理特征要么不可靠(IP 地址在多个请求之间可能会更改),要么容易伪造(例如 User-Agent 请求头),因此可能会导致误报(即真实用户切换了 IP 地址)或漏报(即攻击者能够成功伪造带有相同 User-Agent 的请求)。

这就是为什么防止会话劫持的最佳方法是确保攻击者无法发现其他用户的会话 ID。这意味着您应该设计应用程序及其会话管理,以使 (1) 攻击者无法通过使用足够的熵猜测到有效的会话 ID,(2) 攻击者无法通过已知的攻击/漏洞方式(如嗅探网络通信、跨站点脚本、Referer 泄露等)获取有效的会话 ID。

因此,您应该:

  • 使用带有HttpOnlySecure属性的cookie来禁止JavaScript访问(以防XSS漏洞)以及禁止通过不安全的通道传输(参见session.cookie_httponlysession.cookie_secure)
  • 除此之外,您还应在作废旧会话ID时重新生成会话ID(参见session_regenerate_id函数),例如在登录后进行身份验证或更改授权/权限后。此外,您还可以定期执行此操作,以减少成功攻击的时间间隔。


    6
    +1 我同意。我还要提到会话 ID 必须过期,并且应该使用 /dev/urandom 作为熵文件。此外,会话劫持(也称 CSRF)是一个问题。(参考 http://blog.ptsecurity.com/2012/08/not-so-random-numbers-take-two.html) - rook
    1
    我不明白。如果所有的会话信息都存储在服务器上,那么如何不可能检索存储在自己服务器上的数据呢?我已经查看了PHP的会话处理程序,但我认为它没有实现我所需要的功能,也许我错了? - hesson
    1
    @Rook 我知道Gumbo提出的观点。最重要的是,我想了解PHP是否有一种机制来检索所有活动会话,如果没有,为什么?理论上似乎是可能的,因为PHP会话是服务器端的,所以不具备这样的机制肯定有很好的原因,因为这将对网站的安全性带来巨大的好处。谢谢。 - hesson
    1
    @AnanduMDas 但是你需要解密加密的HTTP消息才能读取cookie。 - Gumbo
    1
    Ini指令session.entropy_filesession.entropy_lengthsession.hash_function已在PHP 7.1中被删除。 - Code4R7
    显示剩余12条评论

    5

    我们能不能做类似这样的事情。

    将会话 ID 存储在数据库中。 同时,为该会话 ID 存储 IP 地址和 HTTP_USER_AGENT。 现在,当一个包含匹配会话 ID 的请求到达服务器时,在你的脚本中检查它来自哪个代理和 IP。

    可以通过为会话创建通用函数或类来使此基础工作,以便在处理之前验证每个请求。这只需要一些微秒的时间。但是,如果许多用户访问您的站点并且您有大量会话数据库,则可能会出现一些性能问题。但是,与其他方法相比,这肯定非常安全,例如 =>使用重新生成会话。

    在重新生成会话 ID 中,会话劫持的机会再次减小。

    假设,用户的会话 ID 被复制,而该用户在一段时间内没有活动并且没有使用旧会话 ID 向服务器发出新会话 ID 的请求。那么如果会话 ID 被劫持,黑客将使用该会话 ID 并向服务器发送请求,然后服务器将用重新生成的会话 ID 响应,以便黑客可以继续使用服务。实际用户将无法操作,因为他不知道重新生成的 ID 是什么以及请求会话 ID 应该传递什么。完全消失了。

    如果我哪里说错了,请纠正我。


    尝试锁定IP地址可能会对移动网络上的任何人造成严重影响(至少在美国),尤其是那些使用CGNAT、许多企业网络和IPv6隐私扩展的人。在这些情况下,特别是在使用NAT时,IP地址可能会频繁变动。 - undefined

    4

    有许多标准的防范会话劫持的方法。其中之一是将每个会话与单个IP地址匹配。

    其他方案可能使用以下内容生成的HMAC:

    • 客户端IP的网络地址
    • 客户端发送的用户代理标头
    • SID
    • 存储在服务器上的秘密密钥

    仅使用IP的网络地址的原因是,如果用户在公共代理后面,则其IP地址可以随每个请求更改,但网络地址保持不变。

    当然,要真正安全,您应该强制所有请求使用SSL,以便SID不能在第一时间被攻击者拦截。但并非所有网站都这样做(:: 咳咳 :: Stack Overflow :: 咳咳 ::)。


    4
    IP地址不切实际,检查用户代理是笑话,特别是如果它存储在cookie值中,因此攻击者知道UA是什么,如果他拥有会话令牌(但如果他知道cookie值,他应该已经知道了)。您没有提到cookie标志。请阅读gumbo的答案。此外,请勿构建自己的会话处理程序,PHP的会话处理程序比此更好。 - rook
    1
    一个hmac不是加密的方法。另外,如果会话ID被XSS或OWASP A9违规使用窃取,则攻击者也将拥有用户代理。 - rook
    1
    hmac 意味着“散列消息认证码”,散列不等于加密,认证与保密不同,通常您需要两者兼备。此外,我强烈不喜欢您的会话处理程序。Gumbo 给出了正确的答案。 - rook
    3
    永远不要混淆哈希函数和加密函数,这可能是发现非密码学家最好的方法。如果你不相信我,请尝试在 http://crypto.stackexchange.com/ 上发布。同时,您不能依赖于攻击者控制的变量来执行访问控制。这就像你建议有一个名为is_hacker=false的 cookie 变量一样。 - rook
    1
    欺骗HMAC?你到底在说什么?我是指这个:https://addons.mozilla.org/en-US/firefox/addon/user-agent-switcher/。你没有提出一个安全系统,因为你依赖于攻击者控制的变量,攻击者将永远知道它。如果你有会话ID,你将始终拥有极易预测的用户代理。所以请删除这篇可怕的帖子,并永远不要向任何人推荐这个系统! - rook
    显示剩余7条评论

    3
    会话劫持是一种严重的威胁,必须使用安全套接字层来处理涉及交易的高级应用程序,或者使用简单的技术,例如使用cookies、会话超时和重新生成id等方法来进行处理。当互联网诞生时,HTTP通信被设计为无状态的;也就是说,两个实体之间的连接只存在于需要将请求发送到服务器并将响应返回到客户端的短暂时间内。
    以下是黑客用来劫持会话的几种方法:
    • 网络窃听
    • 无意中暴露
    • 转发、代理和钓鱼
    • 反向代理
    总是建议使用SSL(安全套接字层)。此外,在脚本开头使用以下ini_set()指令之一,以覆盖php.ini中的任何全局设置:
    使用cookies
    ini_set( 'session.use_only_cookies', TRUE );                
    ini_set( 'session.use_trans_sid', FALSE );
    

    使用会话超时和会话重新生成 ID

    <?php
    // regenerate session on successful login
    if ( !empty( $_POST['password'] ) && $_POST['password'] === $password )         
    {       
     // if authenticated, generate a new random session ID
      session_regenerate_id();
    
     // set session to authenticated
      $_SESSION['auth'] = TRUE;
    
     // redirect to make the new session ID live
    
     header( 'Location: ' . $_SERVER['SCRIPT_NAME'] );
    }                   
    // take some action
    ?>
    

    $_SESSION['auth'] = TRUE;的意义是什么? 如果会话被劫持,所有$_SESSION变量都会被继承,包括这一个。 - ner0

    1
    在我看来,当用户登录时,您可以将会话ID存储在数据库中,并在登录之前检查每个人是否相同。当用户注销时,删除您在数据库中存储的相同会话ID。您可以轻松找到每个用户的会话ID,否则我可以帮助您。

    2
    将会话 ID 存储在数据库中会削弱会话的安全性。攻击者可以利用 SQL 注入技术窃取该 ID 并在不需要破解密码哈希值的情况下登录系统。建议使用 PHP 的会话处理程序,避免自己编写会话处理代码。 - rook
    @Rook,我看了一下PHP的会话处理程序,但我不确定如何将其用作解决我的问题的方案。如果您知道如何实现PHP的会话处理程序以拒绝多个SIDs,请在答案中发布。 - hesson
    @hesson Gumbo的回答很好。您可以使用其他人使用的保护系统。此外,请确保您的系统没有XSS漏洞和OWASP A9违规行为,毕竟这是导致会话妥协的最常见的两个漏洞。 - rook
    1
    @Rook,PHP的会话处理程序支持数据库存储。 - Jacco
    4
    如果你的网站存在SQL注入漏洞,那么你已经遇到了严重的问题。解决方法不是将SID从数据库中移出(这是一个完全合理的放置位置),而是要修复你的SQL注入漏洞 - 除非你建议一个安全的网站根本不应该使用SQL数据库。 - Lèse majesté

    1

    其中一种简单的实现方式是在数据库中创建一个表,作为已登录用户,然后在登录时,使用用户名称和其SID更新该表,这将防止其他用户以相同的身份登录。现在,在注销时,只需运行一个简单的查询,删除数据库中已登录的数据,这也可以用于跟踪网站上同时登录的用户。


    1
    这是一个针对不同问题的解决方案。它有助于防止单个用户从多个客户端登录。所讨论的问题是如何防止多个人假装成为同一用户和客户端。 - Zeal

    1
    显然,当您在浏览器中设置会话Cookie时,该Cookie将随请求一起发送。现在,当请求到达时,服务器将在数据库中检查会话ID并授予访问权限。为了防止这种情况的发生,重要的是存储代理和IP,以便在检查之前,服务器确保会话访问权被授予给唯一的客户端,而不是可以被劫持的唯一会话ID。

    0

    @Anandu M Das:

    我相信你所指的可能是在每个会话ID中使用会话令牌。这个网站可以解释会话中使用令牌的用途:

    https://blog.whitehatsec.com/tag/session-token/

    尽管会话令牌很容易被XSS攻击所破坏,但这并不意味着它们永远不应该使用。我是说,面对现实吧,如果某个东西在服务器上因为安全漏洞而受到威胁,那不是方法的问题,而是引入了那个漏洞的程序员的问题(以上是Hesson和Rook提出的要点)。

    如果您遵循适当的安全规范和做法,并确保从SQL注入、XSS攻击中保护您的站点,并要求所有会话都通过HTTPS进行管理,那么您可以通过使用存储在会话中的服务器端令牌来轻松管理CSRF可能的攻击,并且每次用户引起对其会话的操纵(如提交$_POST)时更新。此外,永远不要在URL中存储会话或其内容,无论您认为它们已加密得多好。

    当您的用户的安全至关重要时(这应该是的),使用会话令牌将允许提供更好或更先进的功能,同时不会影响他们的会话安全性。


    0

    我对编程部分不是很了解。因此,我可以告诉您一个算法来完成这个过程。如果用户和攻击者在同一个局域网中,并且从该网络中嗅探到了会话ID,则设置SSL之类的东西或将会话Cookie设置为安全和httpOnly是行不通的。

    所以,您可以在用户成功登录应用程序后,为 Web 应用程序的每个页面设置唯一令牌,并在服务器端跟踪这些令牌。这样,如果有效用户发送请求以访问特定页面,则该页面的令牌也将发送到服务器端。由于对于特定会话,这些令牌对于用户是唯一的,即使攻击者可以获取会话ID,他也无法劫持用户的会话,因为他无法向服务器提供有效的令牌。


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