有這樣一個場景——有個使用者初訪并登入了你的網站,然而第二天他又來了,卻必須再次登入。于是就有了“記住我”這樣的功能來友善使用者使用,然而有一件不言自明的事情,那就是這種認證狀态的”曠日持久“早已超出了使用者原本所需要的使用範圍。這意味着,他們可以關閉浏覽器,然後再關閉電腦,下周或者下個月,乃至更久以後再回來,隻要這間隔時間不要太離譜,該網站總會知道誰是誰,并一如既往的為他們提供所有相同的功能和服務——與許久前他們離開的時候别無二緻。
我在談論的就是這個“小家夥”:

反模式
這事情乍一看似乎很明顯,就是說“記住我”功能的應用其實是非常基本的,不是那種很玄乎的東西,可事實上呢?顯然不是。
下面我會講解兩個反模式案例和問題的所在,以及談論如何做是正确的。第一個例子,如圖所示,當你登入的時候:
這一切都非常符合标準,但有趣的事情發生在登入之後,讓我們來看看cookies:
這個cookie相當的給力吧,尤其是被選中的黃色高亮部分。如果你沒勾選“記住我”的話,這些要命的資訊是不會被cookie記錄的,是以那個功能原本也隻是單純為了友善使用者再訪的。在圖中,我的郵箱位址是赤裸裸的,但密碼并非明文。然而不要高興得太早,對着那串似乎堅不可摧的加密字元定睛一看……咦?等一下,這不是Base64編碼嗎!關于Base64編碼,這是一種可以被完整解碼的編碼,這意味着你可以随便去哪個編碼轉換網站比如base64decode.org來做下面這件事:
并不是所有人都把Base64當做“加密”(當然,有的人真會用它來加密重要資訊),雖說它确實是一種合理代替ASCII的方法,但事實上這種編碼的密碼僅僅在剛一進入浏覽器後就會立即轉變為明文。你可能會質疑道 - 這能有多大問題?無論如何它隻是存儲在自己的浏覽器裡,它能怎樣?那到底黑客能不能得到它呢?
下面我會介紹兩個非常簡單的方法,首先提及的内容會和上面說的那種情況有關。Black&Decker暴露了ELMAH日志,在這些日志中存在着完全未經配置的内部伺服器錯誤,而被排除的内容是被記錄的内容的幾萬倍。可是當ELMAH記錄了一個異常,在執行處理之前系統也會記錄所有的請求頭,這意味着這些内容全都進入了cookies。試想那數量龐大的被過濾掉的内容其實全被轉存了起來,這簡直就等于有了一個使用者憑據的資料庫。當然他們應該已經修補了這個問題,但它是一個很好的例子,如何輕而易舉的利用一個非常簡單的配置失誤。
接下來是另一個問題:
這個網站是Aussie Farmers Direct(以下統稱為AFD網),是一個相當典型的表單登陸。那就登入看看,并且讓網站“記住我”,接着就來看cookies吧:
好家夥,還是同樣的狀況,甚至連Base64編碼都沒加。要說這一點有多糟糕,來看你可以做這個:
XSS自己的JSON cookie?當然可以這麼玩,這樣一來,如果你的密碼改了但cookie還不變,等你再次登入的時候,它就試圖用舊密碼登入。嗯?
AFD網并沒有暴露他們的ELMAH日志(PHP好樣的!)但他們還有其他風險,如XSS。有關上述網站的另一件事是,這些存儲在Cookie中的密碼并沒有被标記成HttpOnly,你可以在右邊的cookie清單中看到。這意味着,用戶端腳本可以通路這些cookies,這就等于是隻要你能讓XSS成功,就可以通過讓其他使用者加載XSS payload,竊取包含密碼的cookie,(這樣做的有效方法很多)。缺少了HttpOnly屬性或許是一時馬虎,但問題的核心在于存儲在cookie中的密碼會很容易地通過其他途徑疏漏出去。
還有一個更根本的原因,為什麼這些網站會同時在這點上疏忽大意,盡管他們都在保護自己客戶在其網站上使用的憑據。每當上述網站的客戶勾選了“記住我”功能并向網站上送出請求時,當他們的使用者名和密碼被網站發送到他們的郵箱時,當他們操作eBay或網銀時。要麼密碼是明文,要麼可以通過用戶端腳本擷取,總之密碼總會一絲不挂的躺在浏覽器裡。大量的人有密碼複用(通用)的習慣 ,然而為那些熱愛作死的使用者承擔責任的,卻是我們開發者。我不得不說,當我們應對那些使用者憑據威脅的時候,需要實施的保護措施要遠遠超過網站本身。
是以,應該意識到對于如何建立“記住我”功能上的真正誤解,下面我們再來看看良性實踐。
執行個體
在安全界混,有句話你應該會很熟悉,“不用你自己試 - 就用那個公認最給力的就行”。這類話在加密和認證計劃中非常容易聽到,亦可應用在本文課題,借鑒過來深入研究細節。
在一個用Visual Studio 2012建設的ASP.NET MVC 4網站中,你會看到這個:
其他架構有其他的标準模式來實作此功能,但這個更容易作為參考。當我們通過送出上面的表單進行登入時,并且不要求“記住我”,以下的cookie被成功驗證,結果傳回:
Set-Cookie:.ASPXAUTH=6891A5EAF17A9C35B51C4ED3C473FBA294187C97B758880F9A56E3D335E2F020B86A85E1D0074BDAB2E1C9DBE590AF67895C0F989BA137E292035A3093A702DEC9D0D8089E1D007089F75A77D1B2A79CAA800E8F62D3D807CBB86779DB52F012; path=/; HttpOnly
這是一個簡單的身份驗證cookie,并捆綁了所有HTTP的資料。這種我專屬的cookie被發送後,網站就每次都能知道這是我而且我已經被驗證了。我們還能看得更清楚,比如當我們用Chrome’s Cookies collection的時候:
另外,第二個cookie是一個防僞标記,以防止CSRF攻擊,并且與我們的認證狀态無關。除此之外,有沒有其他的cookie。
現在,讓我們勾選上“記住我”然後再次登入看看cookie的響應:
Set-Cookie:.ASPXAUTH=3A92DAC7EFF4EE5B2027A13DD8ABEA8254F0A16D8059FCAF60F5533F1B7D99462DDF57320D069A493481978750526DF952D5C9EA0371C84F5CF1BFC0CCA024C2052824D4BA09670A42B85AEC7FFCB4088FC744C6C0A22749F07AF6E65E674A4A; expires=Tue, 02-Jul-2013 00:27:05 GMT; path=/; HttpOnly
來了你看到沒?來用Chrome解剖一下看看:
我們現在有了一個有效期為48小時的cookie,如果當它過了期限,它在浏覽器關閉時,将被丢棄。讓我們來仔細看看。
尋找身份驗證Cookie的時限部分
其實,這是一個簡單得略顯荒謬的安全構造,我都甚至認為它沒有寫出來的價值,來看一下這裡:在這個例子中,“記住我”功能可以簡單地歸結為,它控制了cookie的時限并且決定某個人能夠持續登入多久。
在上面的例子中,ASP.NET預設使用一個會話cookie,或者換句話說,一個cookie,而且沒有一個明确的截止日期,是以将在浏覽器關閉時強行過期。這是一種方法,另一種是直接置入短保存期限,即使浏覽器繼續使用該cookie,使用者也将被自動登出。當然,你也可以在伺服器上控制這種行為,你也可以讓身份驗證cookie的時限不斷延長,如果系統正在積極使用由伺服器響應增加的時限。
隻要保持這個驗證cookie有效,特定的人就會被記住。那多久的時效才合适呢?上面的例子中預設為2天,但這對于合法的使用者顯然有些過短。而Facebook的cookie卻能持續一年。持續時間較短,意味着更少的風險,但更多的不便,持續時間較長,使得它更容易為使用者增加潛在的風險。讓我們更進一步的看看這個風險。
長期認證狀态的利用
當在被認證之前,你的會話無法被劫持。廢話,這是當然的!但認真看的話,像在上面AFD網的那種情況下,該cookie将在6個月後到期,與此同時它沒有HTTP only的标記,這樣一來他們網站的XSS漏洞可以為攻擊者提供半年的時間去擷取并使用使用者的憑證。同樣的情況,如果時限為1個月,他們仍會有一些嚴重的漏洞,但上述攻擊的機會實實在在地得到了削減。
另一方面,Black&Decker網有一個相對短的——一周的期限。在他們暴露着ELMAH日志的情況下,依然有一系列的重大疏漏,但除非有人在一周前已經用“記住我”登入了網站,并且觸發了那個預設配置的漏洞,否則憑據不會被洩露。如果你想找個網站自己試試看的話,就算是一個你已經登入過的網站,也可以看到存在時限風險的cookie。
是以說一切有關身份驗證的cookie如果想要保護好使用者憑據的話,HttpOnly的安全屬性是和嚴謹的安全态度必須的。雖然所有經典的劫持威脅仍然存在,不過,解決這些cookie上的問題也是絕不容忽視的。
歸根結底,這是一個權衡,需要考慮的因素如攻擊者要取得的資料的價值,在加強驗證安全性時對于使用者使用的便捷性和網站安全配置所造成的負面影響。例如,Facebook中存在着一些非常有用的社會性的使用者資料,而使用者又非常希望無延時般的響應速度,在此之上他們還對自己的賬戶安全上進行了大筆的投資。而對于AFD網,在持有使用者個人身份資料以及财務資訊的同時,提供了使用者所要求的安全驗證服務,能看出使用者本身也是有相關安全意識的。他們有着迥然不同的風險,這兩個網站對于身份驗證cookie的時限政策應該是完全不同的。
強化
或許有些同學會覺得AUTH cookie很無解,有關安全性的東西總會有一種更好的解決方案,但這是有代價的,安全性取決于你願意付出的的時間、金錢、便利性,而且也總會有人告訴你,你做錯了!讓我們來看看關于使用AUTH cookie時限的一些可能的加強方法。
對于一個長期有效的AUTH cookie,問題在于他們需要有效地保持使用者身份的驗證和面對如CSRF或clickjacking等攻擊風險。當然,還有很多需要利用長期cookie的風險并沒有列出,但這并不影響圍繞防範方法的讨論。還有一點就是當一個專用cookie為使用者在伺服器上提供過有效認證之後,在傳回時它還可以重新開啟另一個認證會話。雖然最初的會話會迅速到期,但關鍵是重新開機的新會話,它會因為使用者勾選了“記住我”并再次登入而進行另外的驗證。
一種驗證方式包括利用使用者的IP位址/使用者代理/其他顯著特點來限制“記住我”的cookie。這能提供一些對cookie劫持的防禦。當然,要在合法使用的情況下進行這些變更。特别是在移動網絡中這類的情況并不少見——用不同的IP回訪一個網站。你的ISP不一定總會提供靜态的IP位址。至于使用者代理,還有浏覽器差異,如Chrome和Firefox的更新簡直恍如隔日。然而除非你刻意去挑選某些優質的代理,否則使用代理将是一個危險的做法。
還有一種程式化的手段,就是保持“記住我”cookie和身份驗證cookie的分離,并使用前者重新驗證使用者的身份,但要額外施加一些限制。實際情況是,某個身份的自動認證狀态的過程,一定會遵循安全模型。緩和措施的結果就是,它會在自動重新驗證之前,向使用者再次索要憑據。這并非創新之舉,你或許會在進行例如網銀彙款時遇到過此類功能。在這裡,我們說身份驗證的使用者面臨的風險很大,因為這種方式很容易被劫持和欺騙。
至于其他加強方法,我們可以在“記住我”cookie被使用後進行複位。這也等于讓它在伺服器端無效,而需要一個獨特且持久的cookie值,例如存在于資料庫和cookie間的一個随機數。這樣有利于確定cookie不會被攻擊者用下面的方式獲得。在這裡的文章中,作者談論了一些關于這類模式的緩解方法。但你需要付出一些額外的工作,并在某種程度上給正常的使用者帶來不便(例如使用者無法跨越多個電腦使用并“記住”自己的憑據)。
最後一件值得提一句的是,同一帳戶下的管理原則,它需要我們去關注并且和“記住我”功能有關。例如,允許單一使用者的多個會話同時驗證?或者使用者一旦更改了密碼要不要将會話斷開?管理者可不可以結束驗證會話?出現了各種各樣的問題,不過這裡要讨論的僅僅是關于如何複原。
什麼時候不該用“記住我”功能?(以及一些替代功能)
有時候,允許一個經過認證的使用者保持認證狀态很長一段時間是毫無意義的。例如銀行,在正常的使用情況下,當你想圖省事非要進行自動登入時,在你走人以後留在那裡的是一個未登出的浏覽器,風險多大不用我說了吧。
但其實還是有一些中間地帶可以采取下面這種做法:
這個登陸框并不是看上去的那麼弱,當你用“記住我”登入過後,再次回訪的時候,網站的會話就過期了,你會看到:
使用者名這個樣子可不是我起的,是因為它連同其他一些資料在cookie中存了三個月。坦白地說,這麼做沒有意義,因為記住使用者名不難啊!
但是,這種情況既不能全否也不能全部肯定,這是個灰色的中間地帶。例如,假設在通過“記住我”功能恢複會話時,重新認證是在主程序之前啟動的。這或許是一個兩全齊美的做法。
總結
這個也不例外,總會有些功能看上去似乎是一個好主意,而且它通常是很容易做好的,至少可以适合大多數的項目。坦率來講,前面兩個例子仍然莫名其妙的讓人頭疼,特别是當你考慮到它本來隻是用于延長cookie時限的。
另外,這篇文章的重點并不局限于分解“記住我”功能的安全結構,你可能能夠自行解決自己的cookie憑據問題,但結合起ELMAH和缺少HTTP only屬性以及XSS漏洞的情況考慮,時刻警惕一個不經意的行為,(行為)雖然看似無害但卻很可能造成一個嚴重的安全風險。