1.1.1 摘要
在日常的互聯網生活當中,我們幾乎都離不開用戶驗證登陸功能,例如:登陸微博,Gmail,博客園,Stackoverflow等網站,這給我們帶來了一些不便,就是要管理一堆的用戶名和密碼,也許有人會說現在很多網站都提供授權驗證登陸功能,其中使用最廣泛的是OAuth驗證機制;在某些情況下,例如一些論壇網站提供微博賬戶登陸功能,它的實現的卻方便了用戶,因為它為用戶開放服務和重用現有的賬戶,把認證過程轉給外部服務。
但是作為開發者的我們還必須考慮到一些用戶會采用內部應用程序授權,所以我們還需要提供內部用戶驗證登陸功能。
本文目錄
1.1.2 正文
定義:
在應用程序中,如果驗證和會話(Session)管理的功能沒有正確實現時,導致攻擊者可以竊取用戶密碼,會話令牌或利用其他漏洞來偽裝成其他用戶身份。
剖析破壞驗證:
在ASP.NET中,會話狀態(Session state):是通過在內存中以字典或哈希表的數據結構來存儲用戶的會話;例如:以哈希表(Key/Value)形式來存儲,那么根據Key值(SessionId)檢索指定的Value值(Session)的時間復雜度是O(1)。
我們知道HTTP是無狀態協議,簡而言之,由於Web服務器不會保留以前請求過程中所使用的變量值和任何信息,所以它會把每個頁面請求都認為是獨立的;但我們知道當用戶成功登陸之后,再訪問其他頁面時是無需重新登陸就可以訪問授權頁面了,這是由於在Web服務器和瀏覽器之間維持着一個Session state。
ASP.NET 會話狀態(Session state)將來自限定時間范圍內的同一瀏覽器的請求標識為一個會話,並提供用於在該會話持續期間內保留變量值的方法。默認情況下,將為所有 ASP.NET 應用程序啟用 ASP.NET 會話狀態。
請求持久化的實現:當會話開始時,ASP.NET應用程序通過向客戶端提供一個唯一的密鑰(SessionId)來管理會話狀態(Session state),這個密鑰(SessionId)存儲在客戶端向服務器發送的每個HTTP請求的cookie中;然后,服務器可以從cookie中讀取密鑰(SessionId),並重新存儲到服務器的會話狀態。
現在我們使用Fiddler查看HTTP請求中的數據如下:
圖1 HTTP請求
我們看到在HTTP請求中,Header的Cookie里包含了SessionId值,然后Web服務器就可以獲取到Cookie中的SessionId值,由於每個會話都擁有一個唯一的SessionId值,所以Web服務根據SessionId來識別不同的會話。
接下來讓我們通過具體的例子說明白吧!
現在讓我們通過一個簡單的登錄頁面來說明會話(Session)的原理:
首先我們設計一個簡單的登界面。
圖2登陸界面
當登陸成功之后,顯示信息和跳轉頁面。(注意:登陸和提示在同一個頁面)
圖3登陸成功
/// <summary> /// Handles the Click event of the btnLogin control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="System.EventArgs"/> /// instance containing the event data.</param> protected void btnLogin_Click(object sender, EventArgs e) { lblUsername.Visible = false; lblpassword.Visible = false; txtPassword.Visible = false; txtUsername.Visible = false; btnLogin.Visible = false; //// After successful authentication, save username into session. Session["Username"] = txtUsername.Text; //// Shows message and redirect link. lblMsg.Visible = true; hlpage.Visible = true; }
圖4跳轉頁面
上面的示例代碼中,我們並沒有給出具體的驗證實現,由於我們關注的是用戶驗證成功之后,程序把用戶信息存儲到Session中,當頁面發生跳轉時,通過Session獲取用戶信息。
現在我們在cookies頁面中,查看當前SessionId的值如下:
圖5會話id
由於數據通過會話狀組織起來,會話是由客戶端瀏覽器指定,並且通過瀏覽器的cookie持久化存儲,現在我們把以上Url復制到其他瀏覽器的地址欄中。
圖6會話
如上圖所示,在Chrome瀏覽器中不能獲取我們之前保存在Session中的Username,而且我們查看Chrome中的SessionId值如下:
圖7會話id
現在我們知道Session可以通過Cookie持久化存儲,但由於不同的瀏覽器都會保存各自的Cookie,導致Session無法跨瀏覽器。
現在我們不把Session保存在Cookie中,而是通過Url直接傳遞到服務器中,這就可以實現Session跨瀏覽器,接下來讓我們看一下ASP.NET中的實現吧!
<system.web> <!--Don't use cookies to persist session--> <sessionState cookieless="true" /> </system.web>
我們在WebConfig中設置cookieless="true",當再次運行Logon頁面時,我們發現Url中嵌入了一個字符串,這是由於cookieless="true"不把Session保存在Cookie中,而是通過在Url中嵌入SessionId進行傳遞。
圖8 Url傳遞SessionId
當我們輸入用戶名和密碼登陸后,顯示用戶登陸成功如下所示:
圖9 Url傳遞SessionId
接着我們點擊鏈接頁面跳轉到Redirect,我們發現SessionId和剛開始登陸時候使用的是同一個,這樣Web服務器就可以根據SessionId獲取會話信息。
圖10 Url傳遞SessionId
由於我們在webconfig中設置cookieless="true",這樣就實現了跨瀏覽器訪問授權頁面,但這也存在一個嚴重的安全問題――Session劫持。
假如我們都在頁面的Url中嵌入SessionId,一旦SessionId被攻擊者獲取他們就無需再登錄就可以訪問授權頁面了,這對用戶來說可是一場災難。
也許有人想如果在Url中嵌入SessionId冒着很大的風險,還不如直接把SessionId存儲在Cookie中;但是使用Cookie持久化也可能發生Session劫持,由於Cookie是一個Key/Value的集合,攻擊者可以通過類似XSS或其他攻擊獲取用戶的Cookie。
幸運的是ASP.NET中把所有的Cookie都標記為HttpOnly,這使得攻擊者無法通過客戶端腳本(如document.cookie)獲取瀏覽器的Cookie,例如:通過跨站腳本(XSS, Cross Site Script)漏洞進行攻擊。
HttpOnly是Set-Cookies中一個附加標記,如果在HTTP響應頭中包含HttpOnly標志(可選),那么將不能通過客戶端腳本訪問Cookie(再次瀏覽器是否支持此標志)。因此,即使存在跨站點腳本(XSS)漏洞,也不能訪問瀏覽器的Cookie。(HttpOnly是由微軟在2000年IE6 Sp1中率先發明並予以支持的)
從表中我們可以看出,目前主流的瀏覽器,除了Android之外,幾乎都無一例外對HttpOnly屬性予以了支持。
Browser |
Version |
Prevents Reads |
Prevents Writes |
Microsoft Internet Explorer |
10 |
Yes |
Yes |
Microsoft Internet Explorer |
9 |
Yes |
Yes |
Microsoft Internet Explorer |
8 |
Yes |
Yes |
Microsoft Internet Explorer |
7 |
Yes |
Yes |
Microsoft Internet Explorer |
6 (SP1) |
Yes |
No |
Microsoft Internet Explorer |
6 (fully patched) |
Yes |
Unknown |
Mozilla Firefox |
3.0.0.6+ |
Yes |
Yes |
Netscape Navigator |
9.0b3 |
Yes |
Yes |
Opera |
9.23 |
No |
No |
Opera |
9.5 |
Yes |
No |
Opera |
11 |
Yes |
Unknown |
Safari |
3 |
No |
No |
Safari |
5 |
Yes |
Yes |
iPhone (Safari) |
iOS 4 |
Yes |
Yes |
Google's Chrome |
Beta (initial public release) |
Yes |
No |
Google's Chrome |
12 |
Yes |
Yes |
Android |
Android 2.3 |
Unknown |
Unknown |
ASP.NET membership和role providers的用戶驗證功能
現在,我們對驗證破壞和會話管理有了初步的了解,接下來我們使用ASP.NET中提供的membership和role providers來解決用戶驗證問題吧!
.NET Framkework 2.0中微軟提出了提供者模式(Provider)而且membership和role provider也是基於該模式實現的,在NET2.0或之后的版本,已經提供解決涉及到用戶身份驗證和訪問權限管理方法,它們都是提供者模式的。 而且常用功能如帳戶的創建、驗證、授權和密碼提醒等,這方便了開發者無需從頭開始創建,減少引入不安全的代碼。
相信許多人都實現過頁面登陸功能,這個看似簡單的功能,但實際實現起來時很復雜的,由於.NET中提供LoginView控件已經實現了功能,它整合了用戶身份驗證和訪問管理。
除了LoginView控件之外,我們還可以在Visual Studio工具箱中找到其他類似的控件。這些控件都集成了一些常用的功能(如:驗證登陸,登陸狀態和修改密碼等),這樣我們就無需編寫重復的代碼就可以很好的實現用戶驗證登陸功能。
圖11 Login控件
會話有效期
由於會話(Session)是有生命周期的,在發生會話劫持時,如果該會話已經過期,那么就可以降低被劫持的風險,我們是否應該把會話的生命周期設置的很短呢?回答是否定的,如果會話很快就過期,那么用戶就要不斷重新登錄才能訪問授權頁面,這樣的用戶體驗效果是很差;所以我們必須在衡量風險和體驗效果前提下設置timeout值。
默認情況下,ASP.NET中在不活動情況下Session的timeout值為30分鍾。我們可以通過在web.config中設置Session的timeout屬性來修改過期時間,這里我們把Session過期時間設置為10分鍾,具體實現如下:
<system.web> <!--Setting session expiration date--> <sessionState timeout="10" /> </system.web>
前面我們在程序中設置了會話過期時間,當然用戶也可以通過手動Log out使會話過期。
加密
應該牢記在心充足的數據加密可以確保用戶身份驗證數據安全性,實現安全認證憑據披露的影響明顯和加密的緩解需要發生在兩個關鍵層的認證過程:
1.通過存儲持久數據層的加密(關於數據加密請參看這里和這里)。
2.正確使用 SSL。
Cookie一個不太常被使用的屬性是Secure. 這個屬性啟用時,瀏覽器僅僅會在HTTPS請求中向服務端發送Cookie內容。如果你的應用中有一處非常敏感的業務,比如登錄或者付款,需要使用HTTPS來保證內容的傳輸安全;而在用戶成功獲得授權之后,獲得的客戶端身份Cookie如果沒有設置為Secure,那么很有可能會被非HTTPS頁面中拿到,從而造成重要的身份泄露。所以,在我們的Web站點中,如果使用了SSL,那么我們需要仔細檢查在SSL的請求中返回的Cookie值,是否指定了Secure屬性。
1.1.3 總結
本文介紹了驗證破壞和會話劫持攻擊,通過具體的例子介紹了會話(Session)在瀏覽器中的工作原理,通過Cookie來保持身份認證的服務端狀態,這種保持可能是基於會話(Session)的,也可以是通過在Url中嵌入SessinId持久化。
接着我們介紹了.NET中用戶驗證功能——ASP.NET membership和role providers的用戶驗證功能。
最后,介紹通過設置合適的會話有效期和加密驗證信息可以更好的確保用戶驗證的安全性。
http://msdn.microsoft.com/en-us/library/ms178581(v=vs.90).aspx
http://msdn.microsoft.com/en-us/library/a28ctsa5.aspx
http://www.infoq.com/cn/articles/cookie-security