ASP.NET Forms Authentication所生成Cookie的安全性 [原創 By Fancyf(Fancyray)]

我做這個實驗是因爲http://community.csdn.net/Expert/topic/3927/3927012.xml?temp=.3752405
    最初我想,.NET的驗證應該是比較安全的吧,生成的Cookie也應該與這臺電腦的獨特的參數相關,拿到另一臺電腦上就應該無效了。那麼是不是一個用戶名對應一個Cookie值呢?是否能夠通過僞造Cookie值來騙過表單驗證呢?做一番試驗。
    Web.config修改如下:
    <authentication mode="Forms">
  <forms name="MyLab" loginUrl="/Login.aspx">
   <credentials passwordFormat="Clear">
    <user name="Fancyray" password="Fancyray"/>
   </credentials>
  </forms>
    </authentication>

    <authorization>
        <deny users="?" />
    </authorization>

    Login.aspx只有一個用戶名輸入框txtUsername、一個密碼輸入框txtPassword和一個提交按鈕,Click事件如下:

1if (FormsAuthentication.Authenticate(this.txtUsername.Text, this.txtPassword.Text))
2{
3    FormsAuthentication.RedirectFromLoginPage(this.txtUsername.Text, true);
4}

5else
6{
7    Response.Write("Login denied");
8}
    藉助ieHttpHeaders(http://www.blunck.info/)可以看到,通過驗證後增加了一個類似這樣的Cookie:
MyLab=3FF83247C29EB5D14D61F389D453EEE0586B94E27609C321B017BE7B88D1A94D249996428A7A18F5C2D69F3C4DD2B88C00172CAFB0B4B4ED8784DB62D1D61BCC0C786B4EA7868FC6
    看來這就是加密以後的Cookie了。下面要換一臺電腦,直接將這個值設置爲Cookie,看看是否需要Forms驗證。
    在Login.aspx頁面中加上這樣一句話:
     <script language=javascript>
 document.cookie="MyLab=3FF83247C29EB5D14D61F389D453EEE0586B94E27609C321B017BE7B88D1A94D249996428A7A18F5C2D69F3C4DD2B88C00172CAFB0B4B4ED8784DB62D1D61BCC0C786B4EA7868FC6";
 </script>
    這樣只要一打開Login.aspx頁面就會自動加入這個Cookie。
    另一臺電腦:輸入同一個WebApplication下的另外一個頁面(應該會自動跳轉到Login.aspx頁面)http://10.0.0.7/upload.aspx,這時成功跳轉到了http://10.0.0.7/Login.aspx?ReturnUrl=%2fupload.aspx,正常。這時Cookie的值應該已經生效了。那麼我們再輸入剛纔那個頁面的網址http://10.0.0.7/upload.aspx
    按照我的猜想,肯定還會跳到login.aspx頁面的,因爲那個cookie是在另一臺電腦上生成的。實際呢,沒有跳轉!完整地顯示出了upload.aspx的內容!而我們根本沒有在這臺電腦上登錄,甚至我們連用戶名都不知道!
    我回到10.0.0.7這臺電腦在upload.aspx頁面的Page_Load()第一行添了一句:Response.Write(User.Identity.Name);,在另一臺電腦上刷新顯示出來了的upload.aspx,結果也出現了Fancyray,正是我的用戶名。
    這說明,Cookie的加密不依賴於登錄的電腦。也就是說,一旦你的Cookie被別人獲得,他就有可能獲得你在這臺服務器上的權限。
    那麼Cookie的這個值是怎麼來的呢?黑客是否可能不通過窮舉就得到這個值呢?


    我們先來看看Cookie中到底存儲了一些什麼,以及怎樣進行的加密。Reflactor(http://www.aisto.com/roeder/dotnet)上場!
public static void SetAuthCookie(string userName, bool createPersistentCookie, string strCookiePath)
{
      FormsAuthentication.Initialize();
      HttpContext.Current.Response.Cookies.Add(FormsAuthentication.GetAuthCookie(userName, createPersistentCookie, strCookiePath));
}

public static HttpCookie GetAuthCookie(string userName, bool createPersistentCookie, string strCookiePath)
{
      FormsAuthentication.Initialize();
      if (userName == null)
      {
            userName = "";
      }
      if ((strCookiePath == null) || (strCookiePath.Length < 1))
      {
            strCookiePath = FormsAuthentication.FormsCookiePath;
      }
      FormsAuthenticationTicket ticket1 = new FormsAuthenticationTicket(1, userName, DateTime.Now, createPersistentCookie ? DateTime.Now.AddYears(50) : DateTime.Now.AddMinutes((double) FormsAuthentication._Timeout), createPersistentCookie, "", strCookiePath);
      string text1 = FormsAuthentication.Encrypt(ticket1);
      FormsAuthentication.Trace("ticket is " + text1);
      if ((text1 == null) || (text1.Length < 1))
      {
            throw new HttpException(HttpRuntime.FormatResourceString("Unable_to_encrypt_cookie_ticket"));
      }
      HttpCookie cookie1 = new HttpCookie(FormsAuthentication.FormsCookieName, text1);
      cookie1.Path = strCookiePath;
      cookie1.Secure = FormsAuthentication._RequireSSL;
      if (ticket1.IsPersistent)
      {
            cookie1.Expires = ticket1.Expiration;
      }
      return cookie1;
}
    Cookie中存儲的值就是裏面的text1,text1是由string text1 = FormsAuthentication.Encrypt(ticket1);生成的,所以text1裏面的信息就是ticket1了。FormsAuthenticationTicket的構造函數原形爲:
public FormsAuthenticationTicket(int version, string name, DateTime issueDate, DateTime expiration, bool isPersistent, string userData, string cookiePath)
    裏面有用戶名、生成ticket1的時間和過期時間。
    看到這裏我不禁打了一個冷顫。ticket1實際上只用到了用戶名一個關鍵信息,連密碼都沒有用!這樣豈不是任何一個用戶的ticket1都可以輕易的製造出來嗎?只要通過FormsAuthentication.Encrypt(ticket1)就得到了Cookie的值,來僞裝成任何一個用戶?太可怕了。現在只能寄希望於Encrypt這個函數了。看看它的實現:
public static string Encrypt(FormsAuthenticationTicket ticket)
{
      if (ticket == null)
      {
            throw new ArgumentNullException("ticket");
      }
      FormsAuthentication.Initialize();
      byte[] buffer1 = FormsAuthentication.MakeTicketIntoBinaryBlob(ticket);
      if (buffer1 == null)
      {
            return null;
      }
      if (FormsAuthentication._Protection == FormsProtectionEnum.None)
      {
            return MachineKey.ByteArrayToHexString(buffer1, 0);
      }
      if ((FormsAuthentication._Protection == FormsProtectionEnum.All) || (FormsAuthentication._Protection == FormsProtectionEnum.Validation))
      {
            byte[] buffer2 = MachineKey.HashData(buffer1, null, 0, buffer1.Length);
            if (buffer2 == null)
            {
                  return null;
            }
            FormsAuthentication.Trace("Encrypt: MAC length is: " + buffer2.Length);
            byte[] buffer3 = new byte[buffer2.Length + buffer1.Length];
            Buffer.BlockCopy(buffer1, 0, buffer3, 0, buffer1.Length);
            Buffer.BlockCopy(buffer2, 0, buffer3, buffer1.Length, buffer2.Length);
            if (FormsAuthentication._Protection == FormsProtectionEnum.Validation)
            {
                  return MachineKey.ByteArrayToHexString(buffer3, 0);
            }
            buffer1 = buffer3;
      }
      buffer1 = MachineKey.EncryptOrDecryptData(true, buffer1, null, 0, buffer1.Length);
      return MachineKey.ByteArrayToHexString(buffer1, buffer1.Length);
}
 
    看到了MachineKey這個詞,終於鬆了一口氣。看來加解密過程是與服務器的參數有關係。也就是說,服務器上有自己的密鑰,只有用這個密鑰才能進行Cookie的加解密。如果不知道這個密鑰,別人是無法僞造Cookie的。

    看來Cookie還是安全的,在你的電腦沒有被入侵的前提下。與其他任何信息一樣,所需注意的僅僅是網絡傳輸中的安全性了。
    Cookie的這個值和SessionID一樣,都是一旦被猜中就可能引發安全問題。但又與SessionID有區別,因爲SessionID總是短暫的,而Cookie的值卻有可能是永遠有效的。Cookie值的長度也讓我們稍稍放了一點心。

    還有一個問題不容忽視。雖然Cookie的值的生成與具體的時間有關,也就是我註銷後再次登陸所生成的Cookie是不一樣的,但是一個合法的Cookie值是永久有效的,不受是否改變密碼及時間的影響。也就是說,我上一次生成的Cookie在我註銷以後拿到另一臺電腦上仍然可以用,只要服務器的MachineKey不變。的確是個安全隱患。我們也只能說:“Cookie的值很長,要窮舉到一個有效的Cookie在有生之年是辦不到的”來找一些安慰。密碼可以通過頻繁的更改來進一步減小窮舉到的可能性,但合法的Cookie卻無法更改。密碼是唯一的,但合法的Cookie值卻不是唯一的。這一切總讓人覺得不太放心。
    也許擔心是多餘的,因爲電子簽名、證書都是建立在“窮舉要付出很大代價”的基礎上的,要是考慮“碰巧被窮舉到”的話,安全就不復存在了。相信在一般的安全領域,Forms生成的Cookie的安全級別還是足夠的。
    放心地去用吧!(又一篇毫無價值的文章,當你沒看過好了)

(測試環境:Windows 2003, .NET Framework 1.1 sp1)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章