這一次主要來觀察HTTP消息頭中客戶端的請求,從中找到一些有意思的內容。
a.htm: <a href=b.htm>to page b</a> b.htm: haha
內容很簡單,就是網頁A中有一個到B的鏈接。把它們放到IIS上,並訪問網頁A,從中再點擊到B的鏈接,於是看到了B頁的“haha”。那么這兩次請求有什么不同嗎?觀察它們所發送的HTTP消息頭,最明顯的區別就是訪問B頁時比訪問A頁時多了一行:Referer: http://localhost/a.htm
這一行就表示,用戶要訪問的B頁是從A頁鏈接過來的。服務器端要想取得這個值也是很容易的,以ASP為例,只需要寫一句 <% =Request.ServerVariables("HTTP_REFERER") %>就可以了。
一些網站通過HTTP_REFERER來做安全驗證,判斷用戶是不是從允許的頁面鏈接來的,而不是直接從瀏覽器上打URL或從其他頁面鏈接過來,這樣可以從一定程度上防止網頁被做非法使用。但從上述原理來看,想要騙過服務器也並不困難,只要手工構造輸入的HTTP消息頭就可以了,其他常用的手段還有通過HOSTS文件偽造域名等。
除了超鏈接以外,還有其他幾種方式會導致HTTP_REFERER信息被發送,如:
1 內聯框架: <iframe src=b.asp></iframe> 2 框架集: <frameset><frame src=b.asp></frameset> 3 表單提交: <form action=b.asp><input type=submit></form> 4 SCRIPT引用:<script src=b.asp></script> 5 CSS引用: <link rel=stylesheet type=text/css href=b.asp> 6 XML數據島: <xml src=b.asp></xml> 7 而以下形式不會發送HTTP_REFERER: 8 script轉向: <script>location.href="b.asp"</script> 9 script開新窗口:<script>window.open("b.asp");</script> 10 META轉向: <meta http-equiv="refresh" content="0;URL=b.asp"> 11 引入圖片: <img src=b.asp>
2、COOKIE
1 <% 2 Dim i 3 i = Request.Cookies("key") 4 Response.Write i 5 Response.Cookies("key") = "haha" 6 Response.Cookies("key").Expires = #2007-1-1# 7 %>
第一次訪問此網頁時,屏幕上一片白,第二次訪問時,則會顯示出“haha”。通過閱讀程序不難發現,屏幕上顯示的內容實際上是COOKIE的內容,而第一次訪問時還沒有設置COOKIE的值,所以不會有顯示,第二次顯示的是第一次設置的值。那么對應的HTTP消息頭應該是什么樣的呢?
第一次請求時沒什么不同,略過第一次返回時消息內容多了下面這一行:
Set-Cookie: key=haha; expires=Sun, 31-Dec-2006 16:00:00 GMT; path=/
很明顯,key=haha表示鍵名為“key”的COOKIE的值為“haha”,后面是這則COOKIE的過期時間,因為我用的中文操作系統的時區是東八區,2007年1月1日0點對應的GMT時間就是2006年12月31日16點。
第二次再訪問此網頁時,發送的內容多了如下一行:Cookie: key=haha
它的內容就是剛才設的COOKIE的內容。可見,客戶端在從服務器端得到COOKIE值以后就保存在硬盤上,再次訪問時就會把它發送到服務器。發送時並沒有發送過期時間,因為服務器對過期時間並不關心,當COOKIE過期后瀏覽器就不會再發送它了。
如果使用IE6.0瀏覽器並且禁用COOKIE功能,可以發現服務器端的set-cookie還是有的,但客戶端並不會接受它,也不會發送它。有些網站,特別是在線投票網站通過記錄COOKIE防止用戶重復投票,破解很簡單,只要用IE6瀏覽器並禁用COOKIE就可以了。也有的網站通過COOKIE值為某值來判斷用戶是否合法,這種判斷也非常容易通過手工構造HTTP消息頭來欺騙,當然用HOSTS的方式也是可以欺騙的。
當用戶請求一個ASP網頁時,在返回的HTTP消息頭中會有一行:
Set-Cookie: ASPSESSIONIDCSQCRTBS=KOIPGIMBCOCBFMOBENDCAKDP; path=/
服務器通過COOKIE的方式告訴客戶端你的SESSIONID是多少,在這里是“KOIPGIMBCOCBFMOBENDCAKDP”,並且服務器上保留了和此SESSIONID相關的數據,當同一用戶再次發送請求時,還會把這個COOKIE再發送回去,服務器端根據此ID找到此用戶的數據,也就實現了服務器端用戶狀態的保存。所以我們用ASP編程時可以使用“session("name")=user”這樣的方式保存用戶信息。注意此COOKIE內容里並沒有過期時間,這表示這是一個當關閉瀏覽器時立即過期的COOKIE,它不會被保存到硬盤上。這種工作方式比單純用COOKIE的方式要安全很多,因為在客戶端並沒有什么能讓我們修改和欺騙的值,唯一的信息就是SESSIONID,而這個ID在瀏覽器關閉時會立即失效,除非別人能在你瀏覽網站期間或關閉瀏覽器后很短時間內知道此ID的值,才能做一些欺騙活動。因為服務器端判斷SESSION過期的方式並不是斷開連接或關閉瀏覽器,而是通過用戶手工結束SESSION或等待超時,當用戶關閉瀏覽器后的一段時間里SESSION還沒有超時,所以這時如果知道了剛才的SESSIONID,還是可以欺騙的。因此最安全的辦法還是在離開網站之前手工結束SESSION,很多網站都提供“Logout”功能,它會通過設置SESSION中的值為已退出狀態或讓SESSION立即過期從而起到安全的目的。
SESSION和COOKIE的方式各有優缺點。SESSION的優點是比較安全,不容易被欺騙,缺點是過期時間短,如果用過在超過過期時間里沒有向服務器發送任何信息,就會被認為超過過期了;COOKIE則相反,根據服務器端設置的超時時間,可以長時間保留信息,即使關機再開機也可能保留狀態,而安全性自然大打折扣。很多網站都提供兩種驗證方式相結合,如果用戶臨時用這台電腦訪問此訪問則需要輸入用戶名和密碼,不保存COOKIE;如果用戶使用的是自己的個人電腦,則可以讓網站在自己硬盤上保留COOKIE,以后訪問時就不需要重新輸入用戶名和密碼了。瀏覽器訪問服務器常用的方式有GET和POST兩種,GET方式只發送HTTP消息頭,沒有消息體,也就是除了要GET的基本信息之外不向服務器提供其他信息,網頁表單(FROM)的默認提交方式就是用GET方式,它會把所有向服務器提交的信息都作為URL后面的參數,如a.asp?a=1&b=2這樣的方式。而當要提交的數據量很大,或者所提交內容不希望別人直接看到時,應該使用POST方式。POST方式提交的數據是作為HTTP消息體存在的,例如,寫一個網頁表單:
<form method=post> <input type=text name=text1> <input type=submit> </form>
訪問此網頁,並在表單中填入一個“haha”,然后提交,可以看到此次提交所發送的信息如下:
POST /form.asp HTTP/1.1
Accept: */*
Referer: http://localhost:8080/form.asp
Accept-Language: zh-cn
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; InfoPath.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)
Host: localhost:8080
Content-Length: 10
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: key=haha; ASPSESSIONIDCSQCRTBS=LOIPGIMBLMNOGCOBOMPJBOKP
text1=haha
前面關鍵字從“GET”變為了“POST”,Content-Type變成了“application/x-www-form-urlencoded”,后面內容並無大變化,只是多了一行:Content-Length: 10,表示提交的內容的長度。空行后面是消息體,內容就是表單中所填的內容。注意此時發送的內容只是“Name=Value”的形式,表單上其他的信息不會被發送,所以想直接從服務器端取得list box中所有的list item是辦不到的,除非在提交前用一段script把所有的item內容都連在一起放到一個隱含表單域中。
如果是用表單上傳文件,情況就要復雜一些了,首先是表單聲明中要加上一句話:enctype='multipart/form-data',表示這個表單將提交多段數據,並用HTML:input type=file來聲明一個文件提交域。表單內容如下:
<form method=post enctype='multipart/form-data'> <input type=text name=text1> <input type=file name=file1> <input type=submit> </form>
我們為text1輸入文字:hehe,為file1選擇文件haha.txt,其內容為“ABCDEFG”,然后提交此表單。提交的完全信息為:
POST /form.asp HTTP/1.1
Accept: */*
Referer: http://localhost:8080/form.asp
Accept-Language: zh-cn
Content-Type: multipart/form-data; boundary=---------------------------7d62bf2f9066c
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; InfoPath.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)
Host: localhost:8080
Content-Length: 337
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: key=haha; ASPSESSIONIDCSQCRTBS=LOIPGIMBLMNOGCOBOMPJBOKP
-----------------------------7d62bf2f9066c
Content-Disposition: form-data; name="text1"
hehe
-----------------------------7d62bf2f9066c
Content-Disposition: form-data; name="file1"; filename="H:\Documents and Settings\Administrator\桌面\haha.txt"
Content-Type: text/plain
ABCDEFG
-----------------------------7d62bf2f9066c--
顯然這個提交的信息要比前述的復雜很多。Content-Type變成了“multipart/form-data”,后面還多了一個boundary,此值是為了區分POST的內容的區段用的,只要在內容中遇到了此值,就表示下面要開始一個新的區段了,每個區段的內容相對獨立。如果遇到的是此值后面連着兩個減號,則表示全部內容到此結束。每個段也分為段頭和段體兩部分,用空行隔開,每段都有自己的類型和相關信息。如第一區段是text1的值,它的名稱是“text1”,值為“hehe”。第二段是文件內容,段首里表明了此文件域的名稱“file1”和此文件在用戶磁盤上的位置,后面就是文件的內容。