会话劫持通常来说是更加普遍的会话攻击,并被归结为所有尝试获得其他用户会话权限的攻击手段。
如同会话固定,如果会话运行机制仅含有 session_start() 是存在风险的,尽管利用它并入容易。
相对于关注会话标识如何不被劫持,这里更加关注如何降低劫持的可能性。当使用一个复杂的方法增加了安全时,实现这个目标也变得复杂了起来。要做到这点,需要先了解成功劫持会话必要的步骤。每一个例子,都假设会话标识已经被妥善处理。(In each scenario, we will assume that the session identifier has been compromised.)
基于最简单的会话模型,成功的会话劫持所需要的全部仅仅是一个合法的会话标识。为了改进这个,需要了解在 HTTP 请求中是否有更多可以用于标识的内容。
由于底层通信协议并没有对 HTTP 层的数据产生影响,所以在 TCP/IP 层信任所有数据是愚蠢的,例如 IP地址。一个用户可以在每次请求都有不同的 IP 地址,同时多个用户可以有相同的 IP 地址。
回想一下典型的 HTTP 请求:
GET / HTTP/1.1
Host: example.org
User-Agent: Mozilla/5.0 Gecko
Accept: text/xml, image/png, image/jpeg, image/gif, */*
Cookie: PHPSESSID=1234
只有 Host 在 http/1.1 中是必须的,所以似乎信任任何数据都是愚蠢的。然而,一致性是必要的,因为这里仅关心没有别有用心的人模拟合法用户的复杂实现。
设想前面的请求含有不同的 User-Agent:
GET / HTTP/1.1
Host: example.org
User-Agent: Mozilla Compatible (MSIE)
Accept: text/xml, image/png, image/jpeg, image/gif, */*
Cookie: PHPSESSID=1234
尽管提供了相同的 cookie,这还可以被认为是同一个用户么?同一个浏览器在两个请求之中修改 User-Agent 头似乎不太可能,不是么?修改一下会话原理,增加额外的检查:
<?php
session_start();
if (isset($_SESSION['HTTP_USER_AGENT']))
{
if ($_SESSION['HTTP_USER_AGENT'] != md5($_SERVER['HTTP_USER_AGENT']))
{
/* Prompt for password */
exit;
}
}
else
{
$_SESSION['HTTP_USER_AGENT'] = md5($_SERVER['HTTP_USER_AGENT']);
}
?>
现在攻击者不但必须提供一个合法的会话标识,而且对应的 User-Agent 头也同会话关联起来。让这个更加复杂一些,当然这需要多一些代码。
如何改进这个?考虑最普通的利用浏览器漏洞(如 Internet Explorer)获得 cookie 值的方法。这些方法包括受害者访问了攻击者的站点,这样攻击者可以伪造正确的 User-Agent 头。添加一些必要的信息可以解决这个问题。
设想如果需要用户在每次请求时传递 MD5 编码过的 User-Agent。攻击者不但要构造必要的受害人的请求时的头信息,还要传递额外的数据。虽然猜测这个令牌(token)的构造方法并不是非常困难,仍然可以通过添加一些额外的随机数据让猜测令牌构造方式的工作变得复杂:
<?php
$string = $_SERVER['HTTP_USER_AGENT'];
$string .= 'SHIFLETT';
/* Add any other data that is consistent */
$fingerprint = md5($string);
?>
需要注意,会话标识通过 cookie 传递,必须获得这个 cookie(也有可能需要获得所有 HTTP 头)才可能进行攻击,所以这个 $fingerprint 变量必须通过 URL 地址传递。如同会话标识,这个需要添加在所有的 URL 地址中,因为只有两个条件都满足时会话才可以继续进行(也就是通过所有验证)。
为了确保合法用户不被像嫌疑犯般对待,应当在检查到问题时只是提示输入密码。否则在运行机制中将存在一个把任何用户都看成攻击者的错误,应当在使用这个令人不快的解决方案作为最后手段之前提示输入密码。事实上,在一些操作中用户可能会很赏识这用于安全保护的额外数据。
有许多方法可以让模拟变得复杂并保护应用程序不受会话劫持的攻击。读者自己可能已经有一些添加到 session_start() 的额外的方法 来达到同样的目的。其实只要需要记住让事情对于坏人来说更困难,对于好人来说更容易就可以了。
一些专家认为使用 User-Agent 头对于在讨论中提到的方法来说还不够始终如一。争论在于一组 HTTP 代理中的某个可能修改 User-Agent 头使其同组中其他代理使用的不一致。笔者自己从未考虑过这个问题(同时一直轻松的信任着 User-Agent 的一致性),但读者可能需要考虑这个问题。
已经知道 Accept 头在 Internet Explorer 的每次请求都会发生变化(依赖于用户是否刷新浏览器),所以这个无法被信赖用于上面的目的。