你必須瞭解的Session的本質(PHP版本)
有一點我們必須承認,大多數web應用程式都離不開session的使用。這篇文章將會結合php以及http協議來分析如何建立一個安全的會話管理機制。我們先簡單的瞭解一些http的知識,從而理解該協議的無狀態特性。然後,學習一些關於cookie的基本操作。最後,我會一步步闡述如何使用一些簡單,高效的方法來提高你的php應用程式的安全性以及穩定行。
我想大多數的php初級程式設計師一定會認為php預設的session機制的安全性似乎是有一定保障的,事實恰好相反 – php團隊只是提供了一套便捷的session的解決方案提供給程式設計師使用,至於安全性的話,應該由程式設計師來加強,這是應用程式開發團隊的責任。因為,這裡面的方法很多,可以這麼說吧,沒有最好,只有更好。攻擊的方式在不斷變化,防守方也需要不斷變招,所以,我個人認為php團隊的做法還是比較明智的。
無狀態性
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埠。該個請求的語法如下所示:
1 |
GET
/ HTTP/1.1 |
2 |
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 |
3 |
伺服器端傳送一個http響應到客戶端,其中包含Set-Cookie頭部 |
4 |
5 |
客戶端傳送一個http請求到伺服器端,其中包含Cookie頭部 |
6 |
7 |
伺服器端傳送一個http響應到客戶端 |
這個通訊過程也可以用以下下示意圖來描述:
在客戶端的第二次請求中包含的Cookie頭部中,提供給了伺服器端可以用來唯一標識客戶端身份的資訊。這時,伺服器端也就可以判斷客戶端是否啟用了cookies。儘管,使用者可能在和應用程式互動的過程中突然禁用cookies的使用,但是,這個情況基本是不太可能發生的,所以可以不加以考慮,這在實踐中也被證明是對的。
GET and POST Data
除了cookies,客戶端還可以將傳送給伺服器的資料包含在請求的url中,比如請求的引數或者請求的路徑中。 我們來看一個例子:
1 |
GET
/index.php?foo=bar HTTP/1.1 |
2 |
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的,看下面一個例子: