关于Forms Authentication安全性方面的一些讨论
最近突发奇想,想对微软的.net验证进行测试,是否是如我之前预想的安全。
我最初的想法是,生成的Cookie也应该与这台电脑的独特的参数相关,拿到另一台电脑上就应该无效了。那么是不是一个用户名对应一个Cookie值呢?是否能够通过伪造Cookie值来骗过表单验证呢?做一番试验。
测试如下:
1.我在一台电脑上登陆成功后,找到该生成的cookie:
cookie名:Auth
值:3FF83247C29EB5D14D61F389D453EEE0586B94E27609C321B017BE7B88D1A94D249996428A7A18F5C2D69F3C4DD2B88C00172CAFB0B4B4ED8784DB62D1D61BCC0C786B4EA7868FC6。
2.换另一台电脑,未登陆状态,手动新建一个之前名为Auth的cookies,值相同,域等其他也都相同。刷新页面,结果发现竟然已经登陆成功了,并且在顶部直接显示,欢迎您,zhanghai,而zhanghai就是我从User.Identity.Name取到的,也正是我的登录名。
这说明,Cookie的加密不依赖于登录的电脑。也就是说,一旦你的Cookie被别人获得,他就有可能获得你在这台服务器上的权限。
这和我们的预想完全不同,这是不是一个漏洞,黑客是否能通过某种手段获取得到这个值呢?
解决这个问题,必须先看cookies的加密过程。由于某种原因,此处只贴出网上的一段代码。
1 public static void SetAuthCookie(string userName, bool createPersistentCookie, string strCookiePath) 2 { 3 FormsAuthentication.Initialize(); 4 HttpContext.Current.Response.Cookies.Add(FormsAuthentication.GetAuthCookie(userName, createPersistentCookie, strCookiePath)); 5 } 6 7 public static HttpCookie GetAuthCookie(string userName, bool createPersistentCookie, string strCookiePath) 8 { 9 FormsAuthentication.Initialize(); 10 if (userName == null) 11 { 12 userName = ""; 13 } 14 if ((strCookiePath == null) || (strCookiePath.Length < 1)) 15 { 16 strCookiePath = FormsAuthentication.FormsCookiePath; 17 } 18 FormsAuthenticationTicket ticket1 = new FormsAuthenticationTicket(1, userName, DateTime.Now, createPersistentCookie ? DateTime.Now.AddYears(50) : DateTime.Now.AddMinutes((double) FormsAuthentication._Timeout), createPersistentCookie, "", strCookiePath); 19 string text1 = FormsAuthentication.Encrypt(ticket1); 20 FormsAuthentication.Trace("ticket is " + text1); 21 if ((text1 == null) || (text1.Length < 1)) 22 { 23 throw new HttpException(HttpRuntime.FormatResourceString("Unable_to_encrypt_cookie_ticket")); 24 } 25 HttpCookie cookie1 = new HttpCookie(FormsAuthentication.FormsCookieName, text1); 26 cookie1.Path = strCookiePath; 27 cookie1.Secure = FormsAuthentication._RequireSSL; 28 if (ticket1.IsPersistent) 29 { 30 cookie1.Expires = ticket1.Expiration; 31 } 32 return cookie1; 33 }
由此可见,cookie的值是ticket1加密后得出,ticket1是由public FormsAuthenticationTicket(int version, string name, DateTime issueDate, DateTime expiration, bool isPersistent, string userData, string cookiePath)得到。里面有用户名、生成ticket1的时间和过期时间。注意上面这个方法,有我的用户名,用户数据,路径等等,但是连密码都没有。这样的话,岂不是黑客通过类似加密后,生成cookie,就有所有账号的权限了。这是非常恐怖的一件事。
现在只能寄希望于Encrypt这个函数了。看看它的实现:
1 public static string Encrypt(FormsAuthenticationTicket ticket) 2 { 3 if (ticket == null) 4 { 5 throw new ArgumentNullException("ticket"); 6 } 7 FormsAuthentication.Initialize(); 8 byte[] buffer1 = FormsAuthentication.MakeTicketIntoBinaryBlob(ticket); 9 if (buffer1 == null) 10 { 11 return null; 12 } 13 if (FormsAuthentication._Protection == FormsProtectionEnum.None) 14 { 15 return MachineKey.ByteArrayToHexString(buffer1, 0); 16 } 17 if ((FormsAuthentication._Protection == FormsProtectionEnum.All) || (FormsAuthentication._Protection == FormsProtectionEnum.Validation)) 18 { 19 byte[] buffer2 = MachineKey.HashData(buffer1, null, 0, buffer1.Length); 20 if (buffer2 == null) 21 { 22 return null; 23 } 24 FormsAuthentication.Trace("Encrypt: MAC length is: " + buffer2.Length); 25 byte[] buffer3 = new byte[buffer2.Length + buffer1.Length]; 26 Buffer.BlockCopy(buffer1, 0, buffer3, 0, buffer1.Length); 27 Buffer.BlockCopy(buffer2, 0, buffer3, buffer1.Length, buffer2.Length); 28 if (FormsAuthentication._Protection == FormsProtectionEnum.Validation) 29 { 30 return MachineKey.ByteArrayToHexString(buffer3, 0); 31 } 32 buffer1 = buffer3; 33 } 34 buffer1 = MachineKey.EncryptOrDecryptData(true, buffer1, null, 0, buffer1.Length); 35 return MachineKey.ByteArrayToHexString(buffer1, buffer1.Length); 36 }
看到了MachineKey这个词,终于松了一口气。看来加解密过程是与服务器的参数有关系。也就是说,服务器上有自己的密钥,只有用这个密钥才能进行Cookie的加解密。如果不知道这个密钥,别人是无法伪造Cookie的。
看来Cookie还是安全的,在你的电脑没有被入侵的前提下。与其他任何信息一样,所需注意的仅仅是网络传输中的安全性了。
还有一个问题不容忽视。虽然Cookie的值的生成与具体的时间有关,也就是我注销后再次登陆所生成的Cookie是不一样的,但是一个合法的 Cookie值是永久有效的,不受是否改变密码及时间的影响。也就是说,我上一次生成的Cookie在我注销以后拿到另一台电脑上仍然可以用,只要服务器 的MachineKey不变。的确是个安全隐患。我们也只能说:“Cookie的值很长,要穷举到一个有效的Cookie在有生之年是办不到的”来找一些 安慰。密码可以通过频繁的更改来进一步减小穷举到的可能性,但合法的Cookie却无法更改。密码是唯一的,但合法的Cookie值却不是唯一的。这一切 总让人觉得不太放心。
也许担心是多余的,因为电子签名、证书都是建立在“穷举要付出很大代价”的基础上的,要是考虑“碰巧被穷举到”的话,安全就不复存在了。相信在一般的安全领域,Forms生成的Cookie的安全级别还是足够的。