Session的工作机制详解和安全性问题(PHP实例讲解)

我们先简单的了解一些http的知识,从而理解该协议的无状态特性。然后,学习一些关于cookie的基本操作。最后,我会一步步阐述如何使用一些简单,高效的方法来提高你的php应用程序的安全性以及稳定行。

我想大多数的php初级程序员一定会认为php默认的session机制的安全性似乎是有一定保障的,事实恰好相反 – php团队只是提供了一套便捷的session的解决方案提供给程序员使用,至于安全性的话,应该由程序员来加强,这是应用程序开发团队的责任。因为,这 里面的方法很多,可以这么说吧,没有最好,只有更好。攻击的方式在不断变化,防守方也需要不断变招,所以,我个人认为php团队的做法还是比较明智的。

一、HTTP无状态性

Http是一种无状态性的协议。这是因为此种协议不要求浏览器在每次请求中标明它自己的身份,并且浏览器以及服务器之间并没有保持一个持久性的连接 用于多个页面之间的访问。当一个用户访问一个站点的时候,用户的浏览器发送一个http请求到服务器,服务器返回给浏览器一个http响应。其实很简单的 一个概念,客户端一个请求,服务器端一个回复,这就是整个基于http协议的通讯过程。
因为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的设置以及发送过程分为以下四步:

1.客户端发送一个http请求到服务器端
2.服务器端发送一个http响应到客户端,其中包含Set-Cookie头部
3.客户端发送一个http请求到服务器端,其中包含Cookie头部
4.服务器端发送一个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
[code]   
这两种传递数据的方式,比起用cookies来传递数据更稳定,因为cookie可能被禁用,但是以GET以及POST方式传递数据时,不存在这种情况。我们可以将PHPSESSID包含在http请求的url中,就像下面的例子一样:
[code]    
        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, 对这个例子加以演示:

示例代码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:

示例代码2 – continue.php

复制代码 代码如下:

<!--?php
        session_start();
        echo $_SESSION[‘foo‘]; /* bar */
        ?-->


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

六、session的安全性问题

一直以来很多开发者都认为php内置的session管理机制是具有一定的安全性,可以对一般的session攻击起到防御。事实上,这是一种误
解,php团队只实现了一种方便有效的机制。具体的安全措施,应该有应用程序的开发团队来实施。
就像开篇谈到的,没有最好的解决方案,只有最合适你的方案。

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

1..用户访问http://www.example.org,并且登录。
2.example.org的服务器设置指示客户端设置相关cookie – PHPSESSID=12345
3.攻击者这时访问http://www.example.org/,并且在请求中携带了对应的cookie – PHPSESSID=12345
4.这样情况下,因为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

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

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

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

示例代码 3:

复制代码 代码如下:

<!--?php
            session_start();

if (md5($_SERVER[‘HTTP_USER_AGENT‘]) != $_SESSION[‘HTTP_USER_AGENT‘])
            {   /* 弹出密码输入框 */   exit;
            }

/* 其它代码 */
        ?-->

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

示例代码 4:

复制代码 代码如下:

<!--?php
        session_start();

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

?-->

虽然不一定需要用MD5来加密这个User-Agent信息,但使用这种方式以后就不需要再过滤这
个$_SERVER[‘HTTP_USER_AGENT‘]数据了。不然的话,在使用这个数据以前必须要进行数据过滤,因为任何来自客户端的数据都是不可
信任的,必须要注意这一点。

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

1.获取一个合法的session id
2.包含一个相同的User-Agent头部在伪造的请求中

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

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

复制代码 代码如下:

<!--?php
        session_start();

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

?-->

注意:Accept这个头部不应该被用来生成token,因为有些浏览器会自动改变这个头部,当用户刷新浏览器的时候。

在你的验证机制中加入了这个非常难于猜测出来的token以后,安全性会得到很大的提升。假如这个token通过像session id一样的方式来进行传递,这种情况下,一个攻击者必须完成必要的3步来劫持用户的session:

1.获取一个合法的session ID
2.在请求中加入相同的User-Agent头部,用与生成token
3.在请求中携带被攻击者的token
4.
这里面有个问题。如果session id以及token都是通过GET数据来传递的话,那么对于能获取session
ID的攻击者,同样就能够获取到这个token。所以,比较安全靠谱的方式应该是利用两种不同的数据传递方式来分别传递session
id以及token。例如,通过cookie来传递session
id,然后通过GET数据来传递token。因此,假如攻击者通过某种手段获得了这个唯一的用户身份标识,也是不太可能同时轻松地获取到这个token,
它相对来说依然是安全的。

还有很多的技术手段可以用来加强你的session机制的安全性。希望你在大致了解session的内部本质以后,可以设计出适合你的应用系统的验
证机制,从而大大的提高系统的安全性。毕竟,你是最熟悉当下你开发的系统的开发者之一,可以根据实际情况来实施一些特有的,额外的安全措施。

八、总结

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

时间: 2024-10-10 21:12:22

Session的工作机制详解和安全性问题(PHP实例讲解)的相关文章

HTTP Session、Cookie机制详解

一.什么是http session,有什么用 HTTP协议本身是无状态的,本身并不能支持服务端保存客户端的状态信息,于是,Web Server中引入了session的概念,用来保存客户端的状态信息. 这 里用一个形象的比喻来解释session的工作方式.假设Web Server是一个商场的存包处,HTTP Request是一个顾客,第一次来到存包处,管理员把顾客的物品存放在某一个柜子里面(这个柜子就相当于Session),然后把一个号码牌交给这个顾 客,作为取包凭证(这个号码牌就是Session

JVM结构、GC工作机制详解

JVM结构.内存分配.垃圾回收算法.垃圾收集器. 一.JVM结构 根据<java虚拟机规范>规定,JVM的基本结构一般如下图所示: 从左图可知,JVM主要包括四个部分: 1.类加载器(ClassLoader):在JVM启动时或者在类运行时将需要的class加载到JVM中.(右图表示了从java源文件到JVM的整个过程,可配合理解. 关于类的加载机制,可以参考http://blog.csdn.net/tonytfjing/article/details/47212291) 2.执行引擎:负责执行

业余草分享面试题,JVM结构、GC工作机制详解

题外话:最近在应聘阿里2015暑期实习,感触颇多.机会总是留给有准备的人的,所以平常一定要注意知识的巩固和积累.知识的深度也要有一定的理解,不比别人知道的多,公司干嘛选你?关于JVM和GC,我相信学java的绝大部分人都听过,很多公司的面试官都爱问,一开始我也很头痛,问这么底层干什么,所以我每次面试也只是看看答案敷衍了事.最近面完阿里感觉真不能这样,知识不仅要知其然,还要知其所以然.其实弄懂了JVM和GC,对我们理解很多java知识都有帮助.网上有很多关于GC和JVM的文章,这篇博文主要是根据我

MapReduce工作机制详解

1.MapTask工作机制整个Map阶段流程大体如上图所示.简单概述:input File通过split被逻辑切分为多个split文件,通过Record按行读取内容给map(用户自己实现的)进行处理,数据被map处理结束之后交给OutputCollector收集器,对其结果key进行分区(默认使用hash分区),然后写入buffer,每个map task都有一个内存缓冲区,存储着map的输出结果,当缓冲区快满的时候需要将缓冲区的数据以一个临时文件的方式存放到磁盘,当整个map task结束后再对

linux中fork()函数详解(原创!!实例讲解) (转载)

 一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程, 也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事. 一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间.然后把原来的进程的所有值都 复制到新的新进程中,只有少数值与原来的进程的值不同.相当于克隆了一个自己. 我们来看一个例子: /* * fork_test.c * version 1 *

[置顶] linux中fork()函数详解(原创!!实例讲解)

分类: 计算机系统 linux2010-06-01 23:35 60721人阅读 评论(105) 收藏 举报 linux2010存储  一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事.    一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间.然后把原来的进程的所有值都复制到新的新进程中,

linux中fork()函数详解(原创!!实例讲解)

http://blog.csdn.net/jason314/article/details/5640969 fork()调用将会复制一个与当前进程几乎完全相同(除了fork的返回值不同)的新进程,这两个进程各有各的空间,各有各的局部变量,而且两个进程的局部变量的值在fork这个点处具有相等的值,只有fork返回的值不同,在该例子中,就是局部变量pid的值不同,新进程的pid变量为0,原来进程的pid为新进程的进程ID值.这样,两个并行的进程运行,才会出现你看到的结果. 一.fork入门知识 一个

linux中fork()函数详解(原创!!实例讲解) - jason的专栏 - 博客频道 - CSDN.NET

来源:http://blog.csdn.net/jason314/article/details/5640969#1536434-hi-1-50500-42d97150898b1af15ddaae52f91f09c2

Session机制详解

虽然session机制在web应用程序中被采用已经很长时间了,但是仍然有很多人不清楚session机制的本质,以至不能正确的应用这一技术.本文将详细讨论session的工作机制并且对在Java web application中应用session机制时常见的问题作出解答.     一.术语session 在我的经验里,session这个词被滥用的程度大概仅次于transaction,更加有趣的是transaction与session在某些语境下的含义是相同的. session,中文经常翻译为会话,