设为首页 收藏本站
查看: 762|回复: 0

[经验分享] PHP的Session托管机制容易造成漏洞

[复制链接]

尚未签到

发表于 2017-4-6 07:11:27 | 显示全部楼层 |阅读模式
以下内容针对使用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已经无效,用户当重新登录以维持与服务端的会话连接。

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


对于一个open函数:
function open($sessId) {
$sess = /* 根据$sessId获取到会话数据 */;
if ($sess->isValid()) { /* 检查会话是否有效 */
return $anyVal;
}
return false;
}

open函数,就是读取该客户端持有的sessId,并且读取该会话的value值($_SESSION中的值),这中间PHP会内部将$anyVal进行session_decode()返回到$_SESSION中。
当一个会话的isValid返回false的时候,我们会需要重新生成会话id,并且通知客户端更新。PHP为我们提供了这么一个函数:session_regenerate_id(),但是何时使用这个函数,是整个问题的关键。作为服务器与客户端之间的无缝转换,关键的一步在于dump的时候,偷偷的将新生成的sessId写入记录容器(数据库,内存or I/O),然而dump函数,是作为整个PHP运行过程中的末尾部分,header已经输出,你无法重新改写header。或者简单的说,我们日常编写的PHP代码,无论篇幅大小,都是经由open -> dump这个过程中执行的,当会话机制运行到dump过程时,我们已经无可扭转。
解决此问题的落实点在于最初执行session_start的时候,session最初启动的时候,即可执行session_regenerate_id,但是需要一个全局的监控(这时候js的让人十分怀念,可是php无法做到)。
$sessName = "MY_APP_SID";
$isRegenerateId = false;
session_name($sessName);
session_start(); // => 此处将立刻调用刚才托管的open和pick两个函数
if ($isRegenerateId)
session_regenerate_id();

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

function open($sessId)
....
if ($sess->isValid()) { /* 检查会话是否有效 */
return $anyVal;
}
else {
$isRegenerateId = true;
}
....

dump如何处理,这里就不再例举了,因为当执行了regenerate以后,全局的session_id都会自动的跟随变化。
不过美中不足的是,PHP的OOP的闭合特性不够强大,$isRegenerateId这么一个重要的变量,暴露在任何环境中都是极度危险的,即便使用private也是十分危险的,越是私有,越容易让代码的状态不可控(这方面让人非常羡慕Scala,一个能同时拥有val和var,你只需要声明sealed类作为一个全局的监视器,就能轻松解决此问题,可惜PHP不行)。于是可以有以下方式进行调整:
$sessName = "MY_APP_SID";
session_name($sessName);
session_start(); // => 此处将立刻调用刚才托管的open和pick两个函数
if (defined('REGENERATE_SESS_ID'))
session_regenerate_id();

很自然的,open里面,当isValid为false时,define('REGENERATE_SESS_ID', true)即可。
最后说说,不进行regenerate的后果是什么:
首先,从会话托管的设计初衷而言,让每一个会话本身具有生命周期,就是为了避开沉重的全局回收,让session数据得以冗余而暂时存在,却又不会对该会话持有者造成太大的影响与干扰,如果强行删除会话,客户端必然需要重新登录,这也只是其中的一种情况,如果没有控制好,可能会导致同样的sessId存在多条实例,或者本应超过生命周期的会话重新被激活。当然,也许处在全局的安全性考虑,客户端的个别体验都可以忽略不计,尤其是网站应用方面而言,很少有持续维持会话生命超过十几个小时的。然而这也仅限于网站方面,对于API,或者游戏,对于会话生命周期的要求,就越发的严格。如果站在企业级应用,某些时刻,某些局部,真的是分秒必争。能做到无缝的session生命周期的传承与转换,还是十分重要的。
其次,作为一个系统而言,session往往持有着重大的用户信息,无论怎么完善的系统设计,客户端和服务器之间总需要一条红绳。在特殊应用中,session总是会持有更多特殊的数据,安全的转移,并制造适当的冗余,才能为日后的数据管理,数据回报提供更加完善准确的数据。再者,只有全面的实现设计的全部,才能总结并推演出更加完善的结构,不知道bug的存在,才是最大的风险。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-360737-1-1.html 上篇帖子: PHP中在数组中特定位置增加一个元素 下篇帖子: PHP在双引号中写入变量的注意点
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表