Files
build-web-application-with-…/zh-tw/06.4.md
2019-02-26 01:40:54 +08:00

4.9 KiB
Raw Blame History

6.4 預防session劫持

session劫持是一種廣泛存在的比較嚴重的安全威脅在session技術中客戶端和伺服器端透過session的識別符號來維護會話 但這個識別符號很容易就能被嗅探到,從而被其他人利用。它是中間人攻擊的一種型別。

本節將透過一個例項來示範會話劫持希望透過這個例項能讓讀者更好地理解session的本質。

session劫持過程

我們寫了如下的程式碼來展示一個count計數器


func count(w http.ResponseWriter, r *http.Request) {
	sess := globalSessions.SessionStart(w, r)
	ct := sess.Get("countnum")
	if ct == nil {
		sess.Set("countnum", 1)
	} else {
		sess.Set("countnum", (ct.(int) + 1))
	}
	t, _ := template.ParseFiles("count.gtpl")
	w.Header().Set("Content-Type", "text/html")
	t.Execute(w, sess.Get("countnum"))
}

count.gtpl的程式碼如下所示


Hi. Now count:{{.}}

然後我們在瀏覽器裡面重新整理可以看到如下內容:

圖6.4 瀏覽器端顯示count數

隨著重新整理數字將不斷增長當數字顯示為6的時候開啟瀏覽器(以chrome為例的cookie管理器可以看到類似如下的資訊

圖6.5 取得瀏覽器端儲存的cookie

下面這個步驟最為關鍵: 開啟另一個瀏覽器(這裡我打開了firefox瀏覽器),複製chrome位址列裡的地址到新開啟的瀏覽器的位址列中。然後開啟firefox的cookie模擬外掛新建一個cookie把按上圖中cookie內容原樣在firefox中重建一份:

圖6.6 模擬cookie

Enter後你將看到如下內容

圖6.7 劫持session成功

可以看到雖然換了瀏覽器但是我們卻獲得了sessionID然後模擬了cookie儲存的過程。這個例子是在同一臺計算機上做的不過即使換用兩臺來做其結果仍然一樣。此時如果交替點選兩個瀏覽器裡的連結你會發現它們其實操縱的是同一個計數器。不必驚訝此處firefox盜用了chrome和goserver之間的維持會話的鑰匙即gosessionid這是一種型別的“會話劫持”。在goserver看來它從http請求中得到了一個gosessionid由於HTTP協議的無狀態性它無法得知這個gosessionid是從chrome那裡“劫持”來的它依然會去查詢對應的session並執行相關計算。與此同時 chrome也無法得知自己保持的會話已經被“劫持”。

session劫持防範

cookieonly和token

透過上面session劫持的簡單示範可以瞭解到session一旦被其他人劫持就非常危險劫持者可以假裝成被劫持者進行很多非法操作。那麼如何有效的防止session劫持呢

其中一個解決方案就是sessionID的值只允許cookie設定而不是透過URL重置方式設定同時設定cookie的httponly為true,這個屬性是設定是否可透過客戶端指令碼訪問這個設定的cookie第一這個可以防止這個cookie被XSS讀取從而引起session劫持第二cookie設定不會像URL重置方式那麼容易取得sessionID。

第二步就是在每個請求裡面加上token實現類似前面章節裡面講的防止form重複遞交類似的功能我們在每個請求裡面加上一個隱藏的token然後每次驗證這個token從而保證使用者的請求都是唯一性。


h := md5.New()
salt:="astaxie%^7&8888"
io.WriteString(h,salt+time.Now().String())
token:=fmt.Sprintf("%x",h.Sum(nil))
if r.Form["token"]!=token{
	//提示登入
}
sess.Set("token",token)

間隔產生新的SID

還有一個解決方案就是我們給session額外設定一個建立時間的值一旦過了一定的時間我們銷燬這個sessionID重新產生新的session這樣可以一定程度上防止session劫持的問題。


createtime := sess.Get("createtime")
if createtime == nil {
	sess.Set("createtime", time.Now().Unix())
} else if (createtime.(int64) + 60) < (time.Now().Unix()) {
	globalSessions.SessionDestroy(w, r)
	sess = globalSessions.SessionStart(w, r)
}

session啟動後我們設定了一個值用於記錄產生sessionID的時間。透過判斷每次請求是否過期(這裡設定了60秒)定期產生新的ID這樣使得攻擊者取得有效sessionID的機會大大降低。

上面兩個手段的組合可以在實踐中消除session劫持的風險一方面 由於sessionID頻繁改變使攻擊者難有機會取得有效的sessionID另一方面因為sessionID只能在cookie中傳遞然後設定了httponly所以基於URL攻擊的可能性為零同時被XSS取得sessionID也不可能。最後由於我們還設定了MaxAge=0這樣就相當於session cookie不會留在瀏覽器的歷史記錄裡面。