session劫持是一種廣泛存在的比較嚴(yán)重的安全威脅,在session技術(shù)中,客戶端和服務(wù)端通過(guò)session的標(biāo)識(shí)符來(lái)維護(hù)會(huì)話, 但這個(gè)標(biāo)識(shí)符很容易就能被嗅探到,從而被其他人利用.它是中間人攻擊的一種類(lèi)型。
本節(jié)將通過(guò)一個(gè)實(shí)例來(lái)演示會(huì)話劫持,希望通過(guò)這個(gè)實(shí)例,能讓讀者更好地理解session的本質(zhì)。
我們寫(xiě)了如下的代碼來(lái)展示一個(gè)count計(jì)數(shù)器:
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:{{.}}
然后我們?cè)跒g覽器里面刷新可以看到如下內(nèi)容:
http://wiki.jikexueyuan.com/project/go-web-programming/images/6.4.hijack.png?raw=true" alt="" />
圖6.4 瀏覽器端顯示count數(shù)
隨著刷新,數(shù)字將不斷增長(zhǎng),當(dāng)數(shù)字顯示為6的時(shí)候,打開(kāi)瀏覽器(以chrome為例)的cookie管理器,可以看到類(lèi)似如下的信息:
http://wiki.jikexueyuan.com/project/go-web-programming/images/6.4.cookie.png?raw=true" alt="" />
圖6.5 獲取瀏覽器端保存的cookie
下面這個(gè)步驟最為關(guān)鍵: 打開(kāi)另一個(gè)瀏覽器(這里我打開(kāi)了firefox瀏覽器),復(fù)制chrome地址欄里的地址到新打開(kāi)的瀏覽器的地址欄中。然后打開(kāi)firefox的cookie模擬插件,新建一個(gè)cookie,把按上圖中cookie內(nèi)容原樣在firefox中重建一份:
http://wiki.jikexueyuan.com/project/go-web-programming/images/6.4.setcookie.png?raw=true" alt="" />
圖6.6 模擬cookie
回車(chē)后,你將看到如下內(nèi)容:
http://wiki.jikexueyuan.com/project/go-web-programming/images/6.4.hijacksuccess.png?raw=true" alt="" />
圖6.7 劫持session成功
可以看到雖然換了瀏覽器,但是我們卻獲得了sessionID,然后模擬了cookie存儲(chǔ)的過(guò)程。這個(gè)例子是在同一臺(tái)計(jì)算機(jī)上做的,不過(guò)即使換用兩臺(tái)來(lái)做,其結(jié)果仍然一樣。此時(shí)如果交替點(diǎn)擊兩個(gè)瀏覽器里的鏈接你會(huì)發(fā)現(xiàn)它們其實(shí)操縱的是同一個(gè)計(jì)數(shù)器。不必驚訝,此處firefox盜用了chrome和goserver之間的維持會(huì)話的鑰匙,即gosessionid,這是一種類(lèi)型的“會(huì)話劫持”。在goserver看來(lái),它從http請(qǐng)求中得到了一個(gè)gosessionid,由于HTTP協(xié)議的無(wú)狀態(tài)性,它無(wú)法得知這個(gè)gosessionid是從chrome那里“劫持”來(lái)的,它依然會(huì)去查找對(duì)應(yīng)的session,并執(zhí)行相關(guān)計(jì)算。與此同時(shí) chrome也無(wú)法得知自己保持的會(huì)話已經(jīng)被“劫持”。
通過(guò)上面session劫持的簡(jiǎn)單演示可以了解到session一旦被其他人劫持,就非常危險(xiǎn),劫持者可以假裝成被劫持者進(jìn)行很多非法操作。那么如何有效的防止session劫持呢?
其中一個(gè)解決方案就是sessionID的值只允許cookie設(shè)置,而不是通過(guò)URL重置方式設(shè)置,同時(shí)設(shè)置cookie的httponly為true,這個(gè)屬性是設(shè)置是否可通過(guò)客戶端腳本訪問(wèn)這個(gè)設(shè)置的cookie,第一這個(gè)可以防止這個(gè)cookie被XSS讀取從而引起session劫持,第二cookie設(shè)置不會(huì)像URL重置方式那么容易獲取sessionID。
第二步就是在每個(gè)請(qǐng)求里面加上token,實(shí)現(xiàn)類(lèi)似前面章節(jié)里面講的防止form重復(fù)遞交類(lèi)似的功能,我們?cè)诿總€(gè)請(qǐng)求里面加上一個(gè)隱藏的token,然后每次驗(yàn)證這個(gè)token,從而保證用戶的請(qǐng)求都是唯一性。
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)
還有一個(gè)解決方案就是,我們給session額外設(shè)置一個(gè)創(chuàng)建時(shí)間的值,一旦過(guò)了一定的時(shí)間,我們銷(xiāo)毀這個(gè)sessionID,重新生成新的session,這樣可以一定程度上防止session劫持的問(wèn)題。
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啟動(dòng)后,我們?cè)O(shè)置了一個(gè)值,用于記錄生成sessionID的時(shí)間。通過(guò)判斷每次請(qǐng)求是否過(guò)期(這里設(shè)置了60秒)定期生成新的ID,這樣使得攻擊者獲取有效sessionID的機(jī)會(huì)大大降低。
上面兩個(gè)手段的組合可以在實(shí)踐中消除session劫持的風(fēng)險(xiǎn),一方面, 由于sessionID頻繁改變,使攻擊者難有機(jī)會(huì)獲取有效的sessionID;另一方面,因?yàn)閟essionID只能在cookie中傳遞,然后設(shè)置了httponly,所以基于URL攻擊的可能性為零,同時(shí)被XSS獲取sessionID也不可能。最后,由于我們還設(shè)置了MaxAge=0,這樣就相當(dāng)于session cookie不會(huì)留在瀏覽器的歷史記錄里面。