导读:主要讨论会话和有状态WEB应用的内在风险。
HTTP是一种无状态协议,两个HTTP请求之间缺乏联系,客户端对于服务器来说,是无法区分的。而我们的许多应用,都是需要开发有状态,而对于这些状态的维护是比较麻烦的。cookies实现了这一问题的有力解决。它由两个HTTTP头部组成:Set-Cookie响应头部和Cookie请求头部。当客户端发出一个特定URL的请求时,服务器会在响应时选择包含一个Set-Cookie头部,它要求客户端在接下来的请求中包含一个相应的Cookie头部。这样以来,就可以标示唯一客户端了。
基于会话管理的概念,可以通过管理每一个客户端的各自数据来管理状态。数据被存储在会话存储区,通过每一次请求进行更新。由于会话记录在存储时有唯一的标识,因此它通常被称作 会话标识。
PHP内建的会话机制,并没有安全处理,而且会话标识识随即产生的——不可预测,所以,需要建立相应的安全机制防范会话攻击。
详细:
1 Cookie盗窃
因使用Cookie而产生的一个风险就是用户的Cookie会被攻击者所盗取。从而导致会话劫持的严重风险。
下图为PHP处理相关会话管理的复杂过程:
最常见的cookie暴露原因识浏览器漏洞和XSS。
防范措施就是:防止跨站脚本漏洞和检测导致cookie泄漏的浏览器漏洞相结合。
2 会话数据暴露
会话数据会常暴露一些个人信息和其他敏感数据。
使用SSL进行加密HTTP请求和应答,不失为一种好方法。
如果关心会话数据保存区本身的安全,客户端和服务器端,可以对数据进行加密。PHP中,使用session_set_save_handler进行。
class MySessionHandler implements SessionHandlerInterface { // implement interfaces here } $handler = new MySessionHandler(); session_set_save_handler($handler, true); session_start(); // proceed to set and retrieve values by key from $_SESSION
3 会话固定
关于会话,主要是会话标识的保密性问题。一个攻击者可以通过三种方法来获取合法的会话标识:-猜测 -捕获 -固定。
PHP生成的识随机性很强的会话标识,所以被猜测的风险识不存在的。常见的识通过捕获网络通信数据以得到会话标识,可以使用SSL避免。
分离静态资源:
由于浏览器会根据请求中的Set-Cookie头部中的要求,对之后所有的请求中都包含一个相应的Cookie头部。最常见的是,会话标识会无所谓的在对一些嵌入资源如图片的请求中暴露。为了防止这种无所谓的暴露,可以把所有嵌入资源放在另外一个域名的服务器上。
(看完后半部分,发现自己以前写的代码真是shi,毫无安全可言⋯⋯)
会话不能由URL中的会话标识建立。
初级解决方案:
session_start(); if(!isset($_SESSION['username'])){ session_regenerate_id(); $_SESSION['username] = $clean['username']; }
高级解决方案:
session_start(); $_SESSION['logged_in'] = false; if(check_login()){ session_regenerate_id(); $_SESSION['logged_in'] = true; }
在权限有变更时重新生成会话标识。不建议在每个页面重新生成会话标识。
4 会话劫持
最常见的针对会话的攻击手段时会话劫持。他是所有攻击者可以访问其他人的会话的手段的总称。所有这些手段的第一步都是取得一个合法的会话标识来伪装成合法用户,因为需要保证会话标识不被泄漏。
把伪装过程变得更加复杂是一个好方法,而关键就是加强验证。比如:校验请求的一致性。使用User-Agent检测一致性:
<?php session_start(); if(isset($_SESSION['HTTP_USER_AGENT'])){ if($_SESSION['HTTP_USER_AGENT'] != md5($_SESSION['HTTP_USER_AGENT'])){ # prompt for passcode exit; } } else { $_SESSION['HTTP_USER_AGENT'] = md5($_SESSION['HTTP_USER_AGENT']); }
不可使用Accept头部信息做一致性校验,因为Accept在IE下会变化。
另一方案是,使用URL TOKEN。在URL中传递标识1,在Cookie中传递标识2。这里的标识,最好使用如下方法生成:
session_start(); $token = md5(uniqid(rand(), true)); $_SESSION['token'] = $token;
最后,使用SSL。