我想大多数的php低级程序员一定会认为php默认的session机制的安全性彷佛是有一定保障的,事实恰好相反 - php团队只是供应了一套便捷的session的办理方案供应给程序员利用,至于安全性的话,该当由程序员来加强,这是运用程序开拓团队的任务。
由于,这里面的方法很多,可以这么说吧,没有最好,只有更好。
攻击的办法在不断变革,防守方也须要不断变招,以是,我个人认为php团队的做法还是比较明智的。

无状态性

Http是一种无状态性的协议。
这是由于此种协议不哀求浏览器在每次要求中标明它自己的身份,并且浏览器以及做事器之间并没有保持一个持久性的连接用于多个页面之间的访问。
当一个用户访问一个站点的时候,用户的浏览器发送一个http要求到做事器,做事器返回给浏览器一个http相应。
实在很大略的一个观点,客户端一个要求,做事器端一个回答,这便是全体基于http协议的通讯过程。

php判断session是否存在懂得的Session的实质 HTML

由于web运用程序是基于http协议进行通讯的,而我们已经讲过了http是无状态的,这就增加了掩护web运用程序状态的难度, 对付开拓者来说,是一个不小的寻衅。
Cookies是作为http的一个扩展出身的,其紧张用场是填补http的无状态特性,供应了一种保持客户端与做事器端之间状态的路子,但是由于出于安全性的考虑,有的用户在浏览器中是禁止掉cookie的。
这种情形下,状态信息只能通过url中的参数来通报到做事器端,不过这种办法的安全性很差。
事实上,按照常日的想法,该当有客户端来表明自己的身份,从而和做事器之间坚持一种状态,但是出于安全性方面的考虑,我们都该当明白一点 - 来自客户真个信息都是不能完备信赖的。

只管这样,针对坚持web运用程序状态的问题,相对来说,还是有比较优雅的办理方案的。
不过,该当说是没有完美的办理方案的,再好的办理方案也不可能适用所有的情形。
这篇文章将先容一些技能。
这些技能可以用来比较稳定地坚持运用程序的状态以及抵御一些针对session的攻击,比如会话挟制。
并且你可以学习到cookie是若何事情的,php 的session做了那些事情,以及若何才能挟制session。

HTTP 概览

如何才能保持web运用程序的状态以及选择最得当的办理方案呢?在回答这个问题之前,必须得先理解web的底层协议 - Hypertext Transfer Protocol (HTTP)。

当用户访问http://example.com这个域名的时候,浏览器就会自动和做事器建立tcp/ip连接,然后发送http要求到example.com的做事器的80端口。
该个要求的语法如下所示:

GET / HTTP/1.1

Host: example.org

以上第一行叫做要求行,第二个参数(一个反斜线在这个例子中)表示所要求资源的路径。
反斜线代表了根目录;做事器会转换这个根目录为做事器文件系统中的一个详细目录。

Apache的用户常用DocumentRoot这个命令来设置这个文档根路径。
如果要求的url是http://example.org/path/to/script.php,那么要求的路径便是/path/to/script.php。
如果document root 被定义为usr/lcoal/apache/htdocs的话,全体要求的资源路径便是/usr/local/apache/htdocs/path/to/script.php。

第二行描述的是http头部的语法。
在这个例子中的头部是Host, 它标识了浏览器希望获取资源的域名主机。
还有很多其它的要求头部可以包含在http要求中,比如user-Agent头部,在php可以通过$_SERVER['HTTP_USER_AGENT']获取要求中所携带的这个头部信息。

但是遗憾的是,在这个要求例子中,没有任何信息可以唯一标识当前这个发出要求的客户端。
有些开拓者借助要求中的ip头部来唯一标识发出这次要求的客户端,但是这种办法存在很多问题。
由于,有些用户是通过代理来访问的,比如用户A通过代理B连接网站www.example.com, 做事器端获取的ip信息是代理B分配给A的ip地址,如果用户这时断开代理,然后再次连接代理的话,它的代理ip地址又再次改变,也就说一个用户对应了多个ip地址,这种情形下,做事器端根据ip地址来标识用户的话,会认为要求是来自不同的用户,事实上是同一个用户。
还用其余一种情形便是,比如很多用户是在同一个局域网里通过路由连接互联网,然后都访问www.example.com 的话,由于这些用户共享同一个外网ip地址,这会导致做事器认为这些用户是同一个用户发出的要求,由于他们是来自同一个ip地址的访问。

保持运用程序状态的第一步便是要知道如何来唯一地标识每个客户端。
由于只有在http中要求中携带的信息才能用来标识客户端,以是在要求中必须包含某种可以用来标识客户端唯一身份的信息。
Cookie设计出来便是用来办理这一问题的。

Cookies

如果你把Cookies算作为http协议的一个扩展的话,理解起来就随意马虎的多了,实在实质上cookies便是http的一个扩展。
有两个http头部是专门卖力设置以及发送cookie的,它们分别是Set-Cookie以及Cookie。
当做事器返回给客户端一个http相应信息时,个中如果包含Set-Cookie这个头部时,意思便是指示客户端建立一个cookie,并且在后续的http要求中自动发送这个cookie到做事器端,直到这个cookie过期。
如果cookie的生存韶光是全体会话期间的话,那么浏览器会将cookie保存在内存中,浏览器关闭时就会自动打消这个cookie。
其余一种情形便是保存在客户真个硬盘中,浏览器关闭的话,该cookie也不会被打消,下次打开浏览器访问对应网站时,这个cookie就会自动再次发送到做事器端。
一个cookie的设置以及发送过程分为以下四步:

客户端发送一个http要求到做事器端做事器端发送一个http相应到客户端,个中包含Set-Cookie头部客户端发送一个http要求到做事器端,个中包含Cookie头部做事器端发送一个http相应到客户端

这个通讯过程也可以用以下下示意图来描述:

在客户真个第二次要求中包含的Cookie头部中,供应给了做事器端可以用来唯一标识客户端身份的信息。
这时,做事器端也就可以判断客户端是否启用了cookies。
只管,用户可能在和运用程序交互的过程中溘然禁用cookies的利用,但是,这个情形基本是不太可能发生的,以是可以不加以考虑,这在实践中也被证明是对的。

GET and POST Data

除了cookies,客户端还可以将发送给做事器的数据包含在要求的url中,比如要求的参数或者要求的路径中。
我们来看一个例子:

GET /index.php?foo=bar HTTP/1.1

Host: example.org

以上便是一个常规的http get 要求,该get要求发送到example.org域名对应的web 做事器下的index.php脚本, 在index.php脚本中,可以通过$_GET['foo']来获取对应的url中foo参数的值,也便是’bar’。
大多数php开拓者都称这样的数据会GET数据,也有少数称它为查询数据或者url变量。
但是大家须要把稳一点,不是说GET数据就只能包含在HTTP GET类型的要求中,在HTTP POST类型的要求中同样可以包含GET数据,只要将干系GET数据包含在要求的url中即可,也便是说GET数据的通报不依赖与详细要求的类型。

其余一种客户端通报数据到做事器真个办法是将数据包含在http要求的内容区域内。
这种办法须要要求的类型是POST的,看下面一个例子:

POST /index.php HTTP/1.1

Host: example.org

Content-Type: application/x-www-form-urlencoded

Content-Length: 7

foo=bar

在这种情形下,在脚本index.php可以通过调用$_POST['foo']来获取对应的值bar。
开拓者称这个数据为POST数据,也便是大家熟知的form以post办法提交要求的办法。

在一个要求中,可以同时包含这两种形式的数据:

POST /index.php?myget=foo HTTP/1.1

Host: example.orgContent-Type: application/x-www-form-urlencoded

Content-Length: 11

mypost=bar

这两种通报数据的办法,比起用cookies来通报数据更稳定,由于cookie可能被禁用,但是以GET以及POST办法通报数据时,不存在这种情形。
我们可以将PHPSESSID包含在http要求的url中,就像下面的例子一样:

GET /index.php?PHPSESSID=12345 HTTP/1.1

Host: example.org

以这种办法通报session id的话,可以跟用cookie头部通报session id一样,达到同样的效果, 但是,缺陷便是须要开拓者认为地将session id附加在url中或者作为隐蔽字段加入到表单中。
不像cookie一样,只要做事器端指示客户端创建cookie成功往后,客户端在后续的要求中,会自动第将对应的没有过期的cookie通报给做事器端。
当然,php在开启session.use_trans_sid后,也可以自动地将session id 附加在url中以及表单的隐蔽字段中,但是这个选项不建议开启,由于存在安全问题。
这样的话,随意马虎透露session id, 比如有的用户会bookmark一个url或者分享一个url,那么session id也就暴露了,加入这个session id还没有过期,那是有一定的安全问题存在的,除非做事器端,除了session id外,还附加了其它办法进行验证用户的合法性!

只管以POST的办法来通报session id的话,相对GET的办法来说,会安全的多。
但是,这种办法的缺陷便是比较麻烦,由于这样的话,在你的运用程序中比较将所有的要求都转换成post的要求,这显然是不太得当的。

Session的管理

直到现在,我只谈论了如何掩护运用程序的状态,只是大略地涉及到了如果保持要求之间的关系。
接下来,我阐述下在实际中用到比较多的技能 - Session的管理。
涉及到session的管理,就不是单单地坚持各个要求之间的状态,还须要坚持会话期间针对每个特定用户利用到的数据。
我们常常把这种数据叫做session数据,由于这些数据是跟某个特定用户与做事器之间的会话干系联的。
如果你利用php内置的session的管理机制,那么session数据一样平常是保存在/tmp这个做事器真个文件夹中,并且个中的session数据会被自动地保存到超级数组$_SESSION中。
一个最大略的利用session的例子,便是将干系的session数据从一个页面通报(把稳:实际通报的是session id)到另一个页面。
下面用示例代码1, start.php, 对这个例子加以演示:

Listing 1 - start.php

<?php

session_start();

$_SESSION['foo'] = 'bar';

?>

<a href=\公众continue.php\"大众>continue.php</a>

如果用户点击start.php中的链接访问continue.php,那么在continue.php中就可以通过$_SESSION['foo']获取在start.php中的定义的值’bar’。
看下面的示例代码2:

Listing 2: - continue.php

<?php

session_start();

echo $_SESSION['foo']; / bar /

?>

是不是非常大略,但是我要指出的话,如果你真的这样来写代码的话,解释你对php底层的对付session的实现机制还不是非常理解透彻。
在不理解php内部给你自动做了多少事情的情形下,你会创造如果程序出错的话,这样的代码将变的很难调试,事实上,这样的代码也完备没有安全性可言。

Session的安全性问题

一贯以来很多开拓者都认为php内置的session管理机制是具有一定的安全性,可以对一样平常的session攻击起到防御。
事实上,这是一种误解,php团队只实现了一种方便有效的机制。
详细的安全方法,该当有运用程序的开拓团队来履行。
就像开篇谈到的,没有最好的办理方案,只有最得当你的方案。

现在,我们来看下一个比较常规的针对session的攻击:

用户访问http://www.example.org,并且登录。
example.org的做事器设置指示客户端设置干系cookie - PHPSESSID=12345攻击者这时访问http://www.example.org/,并且在要求中携带了对应的cookie - PHPSESSID=12345这样情形下,由于example.orge的做事器通过PHPSESSID来辨认对应的用户的,以是做事器错把攻击者当成了合法的用户。

全体过程的描述,请看下面的示例图:

当然这种攻击的办法,条件条件是攻击者必须通过某种手段固定,挟制或者预测出某个合法用户的PHPSESSID。
虽然这看起来难度很高,但是也不是不可能的事情。

安全性的加强

有很多技能可以用来加强Session的安全性,紧张思想便是要使验证的过程对付合法用户来说,越大略越好,然后对付攻击者来说,步骤要越繁芜越好。
当然,这彷佛是比较难于平衡的,要根据你运用程序的详细设计来做决策。

没有韶光写下去,待续。


最大略的居于HTTP/1.1要求包括要求行以及一些Host的头部:

GET / HTTP/1.1

Host: example.org

如果客户端通过PHPSESSID通报干系的session标识符,可以将PHPSESSID放在cookie头部中进行通报:

GET / HTTP/1.1

Host: example.org

Cookie: PHPSESSID=12345

同样地,客户端也可以将session标识符放在要求的url中进行通报。

GET /?PHPSESSID=12345

HTTP/1.1Host: example.org

当然,session标识符也可以包含在POST数据中,但是这对用户体验有影响,以是这种办法很少采取。

由于来自TCP/IP信息也不一定可以完备信赖的,以是,对付web开拓者来说,利用TCP/IP中的信息来加强安全性也是不太得当的。
不过,攻击者也必须供应一个合法用户的唯一的标识符,才能假扮成合法用户进入系统。
因此,看起来唯一能够有效的保护系统的方法,便是只管即便地隐蔽session标识符或者使之难于预测出来。
最好便是两者都能履行。

PHP会自动天生一个随机的session ID,基本来说是不可能被预测出来的,以是这方面的安全还是有一定保障的。
但是,要防止攻击者获取一个合法的session ID是相称困难的,这基本上不是开拓者所能掌握的。

事实上,许多情形下都有可能导致session ID的透露。
比如说,如果通过GET数据来通报session ID的话,就有可能暴露这个敏感的身份信息。
由于,有的用户可能会将带有session ID的链接缓存,收藏或者发送在邮件内容中。
Cookies是一种像相对来说安全一点的机制,但是用户是可以在客户端中禁止掉cookies的!
在一些IE的版本中也有比较严重的安全漏洞,比较有名的便是会透露cookies给一些有安全隐患的邪恶站点。

因此,作为一个开拓者,可以肯定session ID是不能被预测出来的,但是还是有可能被攻击者利用某些方法获取到。
以是,必须采纳一些额外的安全方法来防止此类情形在你的运用程序中发生。

实际上,一个标准的HTTP要求中除了Host等必须包含的头部,还包含了一些可选的头部.举一个例子,看下面的一个要求:

GET / HTTP/1.1

Host: example.org

Cookie: PHPSESSID=12345

User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1

Accept: text/html;q=0.9, /;q=0.1

Accept-Charset: ISO-8859-1, utf-8;q=0.66, ;q=0.66

Accept-Language: en

我们可以看到,在以上的一个要求例子中包含了四个额外的头部,分别是User-Agent, Accept, Accept-Charset以及Accept-Language。
由于这些头部不是必须的,以是完备依赖他们在你的运用程序中发挥浸染是不太明智的。
但是,如果一个用户的浏览器确实发送了这些头部到做事器,那么可以肯定的是在接下来的同一个用户通过同一个浏览器发送的要求中,一定也会携带这些头部。
当然,这个中也会有极少数的分外情形发生。
如果以上例子是由一个当前的跟做事器建立了会话的用户发出的要求,考虑下面的一个要求:

GET / HTTP/1.1

Host: example.org

Cookie: PHPSESSID=12345

User-Agent: Mozilla/5.0 (compatible; IE 6.0 Microsoft Windows XP)

由于有相同的session id包含在要求的Cookie头部中,以是相同的php session将会被访问到。
但是,要求里的User-Agent头部跟先前的要求中的信息是不同的,系统是否可以假定这两个要求是同一个用户发出的?

像这种情形下,创造浏览器的头部改变了,但是不能肯定这是否是一次来自攻击者的要求的话,比较好的方法便是弹出一个哀求输入密码的输入框让用户输入,这样的话,对用户体验的影响不会很大,又能很有效地防止攻击。

当然,你可以在系统中加入核查User-Agent头部的代码,类似Listing 3中的代码:

Listing 3:

<?php

session_start();

if (md5($_SERVER['HTTP_USER_AGENT']) != $_SESSION['HTTP_USER_AGENT'])

{ / 弹出密码输入框 / exit;

}

/ Rest of Code / ?>

当然,你先必须在第一次要求时,初始化session的时候,用MD5算法加密user agent信息并且保存在session中,类似下面listing4中的代码:

Listing 4:

<?php

session_start();

$_SESSION['HTTP_USER_AGENT'] = md5($_SERVER['HTTP_USER_AGENT']);

?>

虽然不一定须要用MD5来加密这个User-Agent信息,但利用这种办法往后就不须要再过滤这个$_SERVER['HTTP_USER_AGENT']数据了。
不然的话,在利用这个数据以前必须要进行数据过滤,由于任何来自客户真个数据都是不可信赖的,必须要把稳这一点。

在你检讨这个User-Agent客户端头部信息往后,做为一个攻击者必须要完成两步才能挟制一个session:

获取一个合法的session id包含一个相同的User-Agent头部在假造的要求中

你可能会说,居然攻击者能得到有效的session id,那么以他的水平,假造一个相同的User-Agent不是件难事。
不错,但是我们可以说这至少给他添加了一些麻烦,在一定程度上也增加了session机制的安全性。

你该当也能想到了,既然我们可以检讨User-Agent这个头部来加强安全性,那么不妨再利用其它的一些头部信息,把他们组合起来天生一个加密的token,并且让客户端在后续的要求中携带这个token!
这样的话,攻击者基本上不可能预测出这样一个token是怎么天生出来的。
这好比你用信用卡在超市付款,一个你必须有信用卡(好比session id),其余你也必须输入一个支付密码(好比token),这有这两者都符合的情形下,你才能成功进入账号付款。
看下面一段代码:

<?php

session_start();

$token = 'SHIFLETT' . $_SERVER['HTTP_USER_AGENT'];

$_SESSION['token'] = md5($token . session_id());

?>

把稳:Accept这个头部不应该被用来天生token,由于有些浏览器会自动改变这个头部,当用户刷新浏览器的时候。

在你的验证机制中加入了这个非常难于预测出来的token往后,安全性会得到很大的提升。
如果这个token通过像session id一样的办法来进行通报,这种情形下,一个攻击者必须完成必要的3步来挟制用户的session:

获取一个合法的session ID在要求中加入相同的User-Agent头部,用与天生token在要求中携带被攻击者的token

这里面有个问题。
如果session id以及token都是通过GET数据来通报的话,那么对付能获取session ID的攻击者,同样就能够获取到这个token。
以是,比较安全靠谱的办法该当是利用两种不同的数据通报办法来分别通报session id以及token。
例如,通过cookie来通报session id,然后通过GET数据来通报token。
因此,如果攻击者通过某种手段得到了这个唯一的用户身份标识,也是不太可能同时轻松地获取到这个token,它相对来说依然是安全的。

还有很多的技能手段可以用来加强你的session机制的安全性。
希望你在大致理解session的内部实质往后,可以设计出适宜你的运用系统的验证机制,从而大大的提高系统的安全性。
毕竟,你是最熟习当下你开拓的系统的开拓者之一,可以根据实际情形来履行一些特有的,额外的安全方法。

总结

以上只是大概地描述了session的事情机制,以及大略地阐述了一些安全方法。
但要记住,以上的方法都是能够加强安全性,不是说能够完备保护你的系统,希望读者自己再去调研干系内容。
在这个调研过程中,相信你会学到很有实际利用代价的方案。