首页 > 技术文章 > 关于Forms Authentication安全性方面的一些讨论

zhanghai 2015-05-27 15:50 原文

  最近突发奇想,想对微软的.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的安全级别还是足够的。

推荐阅读