一个分享个人学习、开发经验的Blog,http://www.joyphper.net

PHP的Session托管机制容易造成漏洞

posted @ 2010-11-15 09:20 | 阅读:2722 | 评论:1 | 分类: PHP

  以下内容针对使用PHP的session_set_save_handler的托管机制进行PHP会话管理托管的开发人员。

  假定我们通过open,close,pick,dump,clear,gc六个函数对PHP的会话管理进行托管。PHP的session的gc回收机制本身没有问题,问题在于对于未进行gc回收,而又已经过时了的session。

  PHP应用中,会话管理都可以依赖于传统的gc机制进行回收,然而当一个项目的并发越高,而gc回收的几率越高,对于数据库(或者IO)而言是一大负荷,所以面对并发高的站点,我们都会将gc回收的基数增加(ini_set('session.gc_divisor',xxxx);),这个也不是本文要讨论的问题重点,所以具体就不讨论了。

  当尝试对会话进行托管控制,实际上一方面是为了减轻PHP会话机制本身的一些弊端(减少在$_SESSION变量中存放重要的用户信息),其次,也能帮助我们更好的掌握和管理整个系统的会话。

  一个标准的设计,会话数据本身,会为其设计一个生命周期,当超过声明周期的会话,当用户的客户端再持有该session_id而发生会话处理时,则认为该session已经无效,用户当重新登录以维持与服务端的会话连接。

  1. $sessName = "MY_APP_SID";     
  2. session_name($sessName);     
  3. session_start(); // => 此处将立刻调用刚才托管的open和pick两个函数 

  对于一个open函数:

  1. function open($sessId) {     
  2.     $sess = /* 根据$sessId获取到会话数据 */;     
  3.     if ($sess->isValid()) { /* 检查会话是否有效 */    
  4.         return $anyVal;     
  5.     }     
  6.     return false;     
  7. }   

  open函数,就是读取该客户端持有的sessId,并且读取该会话的value值($_SESSION中的值),这中间PHP会内部将$anyVal进行session_decode()返回到$_SESSION中。

  当一个会话的isValid返回false的时候,我们会需要重新生成会话id,并且通知客户端更新。PHP为我们提供了这么一个函数:session_regenerate_id(),但是何时使用这个函数,是整个问题的关键。作为服务器与客户端之间的无缝转换,关键的一步在于dump的时候,偷偷的将新生成的sessId写入记录容器(数据库,内存orI/O),然而dump函数,是作为整个PHP运行过程中的末尾部分,header已经输出,你无法重新改写header。或者简单的说,我们日常编写的PHP代码,无论篇幅大小,都是经由open->dump这个过程中执行的,当会话机制运行到dump过程时,我们已经无可扭转。

  解决此问题的落实点在于最初执行session_start的时候,session最初启动的时候,即可执行session_regenerate_id,但是需要一个全局的监控(这时候js的让人十分怀念,可是php无法做到)。

  1. $sessName = "MY_APP_SID";     
  2. $isRegenerateId = false;     
  3. session_name($sessName);     
  4. session_start(); // => 此处将立刻调用刚才托管的open和pick两个函数     
  5. if ($isRegenerateId)     
  6.     session_regenerate_id();   

  而在open的函数中,则是用于通知$isRegenerateId是否变更了:

  1. function open($sessId) {     
  2.     ....     
  3.          
  4.     if ($sess->isValid()) { /* 检查会话是否有效 */    
  5.         return $anyVal;     
  6.     }     
  7.     else {     
  8.         $isRegenerateId = true;     
  9.     }     
  10.     ....     
  11. }   

  dump如何处理,这里就不再例举了,因为当执行了regenerate以后,全局的session_id都会自动的跟随变化。

  不过美中不足的是,PHP的OOP的闭合特性不够强大,$isRegenerateId这么一个重要的变量,暴露在任何环境中都是极度危险的,即便使用private也是十分危险的,越是私有,越容易让代码的状态不可控。于是可以有以下方式进行调整:

  1. $sessName = "MY_APP_SID";     
  2. session_name($sessName);     
  3. session_start(); // => 此处将立刻调用刚才托管的open和pick两个函数     
  4. if (defined('REGENERATE_SESS_ID'))     
  5.     session_regenerate_id();   

  很自然的,open里面,当isValid为false时,define('REGENERATE_SESS_ID',true)即可。

  最后说说,不进行regenerate的后果是什么:

  首先,从会话托管的设计初衷而言,让每一个会话本身具有生命周期,就是为了避开沉重的全局回收,让session数据得以冗余而暂时存在,却又不会对该会话持有者造成太大的影响与干扰,如果强行删除会话,客户端必然需要重新登录,这也只是其中的一种情况,如果没有控制好,可能会导致同样的sessId存在多条实例,或者本应超过生命周期的会话重新被激活。当然,也许处在全局的安全性考虑,客户端的个别体验都可以忽略不计,尤其是网站应用方面而言,很少有持续维持会话生命超过十几个小时的。然而这也仅限于网站方面,对于API,或者游戏,对于会话生命周期的要求,就越发的严格。如果站在企业级应用,某些时刻,某些局部,真的是分秒必争。能做到无缝的session生命周期的传承与转换,还是十分重要的。

  其次,作为一个系统而言,session往往持有着重大的用户信息,无论怎么完善的系统设计,客户端和服务器之间总需要一条红绳。在特殊应用中,session总是会持有更多特殊的数据,安全的转移,并制造适当的冗余,才能为日后的数据管理,数据回报提供更加完善准确的数据。再者,只有全面的实现设计的全部,才能总结并推演出更加完善的结构,不知道bug的存在,才是最大的风险。

原文链接:http://my.oschina.net/janpoem/blog/6932

TAG: php学习 , php , session机制

共有1条评论 发表评论>>

Travon 发表于:2016-05-18 19:17
A actually great submit by you my friend. We have bokeramkod this page and will appear back following several days to examine for any new posts that you simply make.
点击换一张验证码