在《ASP.NET虚拟主机中Forms Authentication的安全性》中,我用同一台电脑的一个站点上创建的验证Cookie通过了另一个站点的表单验证,然后我得出一个结论:MachineKey的确只与Machine有关,而与WebApplication无关。在web.config文件<configuration>-<system.web>下的<machineKey>中可能的配置如下:
<machineKey validationKey="AutoGenerate|value[,IsolateApps]"
decryptionKey="AutoGenerate|value[,IsolateApps]"
validation="SHA1|MD5|3DES"/>
MSDN对IsolateApps的解释如下:
If you add the IsolateApps modifier to the validationKey value, ASP.NET generates a unique encrypted key for each application using each application's application ID.
这就是说,加上",IsolateApps"以后每个Web应用程序所用的MachineKey就都不一样了,一个程序中生成的验证Cookie在另一个应用程序中就通不过了。那么我实验的结果是因为没有设置",IsolateApps"吗?我修改了两个Web站点的Web.config文件,分别加上了
<machineKey validationKey="AutoGenerate,IsolateApps"
decryptionKey="AutoGenerate,IsolateApps"
validation="SHA1" />
结果再次出乎我的预料!其中一个站点生成的验证Cookie还是通过了另一个站点的验证。重新启动系统、更换用户名之类我都是过了,但结果没有改变。这与MSDN上说的可不一样。答案也只有用Reflector来寻找了。
找到了分析<machineKey>这一段的代码,位于System.Web.Configuration.MachineKey.MachineKeyConfig..ctor(Object, Object, XmlNode)。代码比较长,不过还是贴出来比较方便。
2 ... {
3 MachineKey.MachineKeyConfig config1 = (MachineKey.MachineKeyConfig) parentObject;
4 HttpConfigurationContext context1 = contextObject as HttpConfigurationContext;
5 if (HandlerBase.IsPathAtAppLevel(context1.VirtualPath) == PathLevel.BelowApp)
6 ...{
7 throw new ConfigurationException(HttpRuntime.FormatResourceString("No_MachineKey_Config_In_subdir"), node);
8 }
9 if (config1 != null)
10 ...{
11 this._ValidationKey = config1.ValidationKey;
12 this._DecryptionKey = config1.DecryptionKey;
13 this._ValidationMode = config1.ValidationMode;
14 this._AutogenKey = config1.AutogenKey;
15 }
16 XmlNode node1 = node.Attributes.RemoveNamedItem("validationKey");
17 XmlNode node2 = node.Attributes.RemoveNamedItem("decryptionKey");
18 int num1 = 0;
19 string[] textArray2 = new string[3] ...{ "SHA1", "MD5", "3DES" } ;
20 string[] textArray1 = textArray2;
21 XmlNode node3 = HandlerBase.GetAndRemoveEnumAttribute(node, "validation", textArray1, ref num1);
22 if (node3 != null)
23 ...{
24 this._ValidationMode = (MachineKeyValidationMode) num1;
25 }
26 HandlerBase.CheckForUnrecognizedAttributes(node);
27 HandlerBase.CheckForChildNodes(node);
28 if ((node1 != null) && (node1.Value != null))
29 ...{
30 string text1 = node1.Value;
31 bool flag1 = text1.EndsWith(",IsolateApps");
32 if (flag1)
33 ...{
34 text1 = text1.Substring(0, text1.Length - ",IsolateApps".Length);
35 }
36 if (text1 == "AutoGenerate")
37 ...{
38 this._ValidationKey = new byte[0x40];
39 Buffer.BlockCopy(HttpRuntime.s_autogenKeys, 0, this._ValidationKey, 0, 0x40);
40 }
41 else
42 ...{
43 if ((text1.Length > 0x80) || (text1.Length < 40))
44 ...{
45 throw new ConfigurationException(HttpRuntime.FormatResourceString("Unable_to_get_cookie_authentication_validation_key", text1.Length.ToString()), node1);
46 }
47 this._ValidationKey = MachineKey.HexStringToByteArray(text1);
48 if (this._ValidationKey == null)
49 ...{
50 throw new ConfigurationException(HttpRuntime.FormatResourceString("Invalid_validation_key"), node1);
51 }
52 }
53 if (flag1)
54 ...{
55 int num2 = SymbolHashCodeProvider.Default.GetHashCode(HttpContext.Current.Request.ApplicationPath);
56 this._ValidationKey[0] = (byte) (num2 & 0xff);
57 this._ValidationKey[1] = (byte) ((num2 & 65280) >> 8);
58 this._ValidationKey[2] = (byte) ((num2 & 16711680) >> 0x10);
59 this._ValidationKey[3] = (byte) ((num2 & 4278190080) >> 0x18);
60 }
61 }
62 if (node2 != null)
63 ...{
64 string text2 = node2.Value;
65 bool flag2 = text2.EndsWith(",IsolateApps");
66 if (flag2)
67 ...{
68 text2 = text2.Substring(0, text2.Length - ",IsolateApps".Length);
69 }
70 if (text2 == "AutoGenerate")
71 ...{
72 this._DecryptionKey = new byte[0x18];
73 Buffer.BlockCopy(HttpRuntime.s_autogenKeys, 0x40, this._DecryptionKey, 0, 0x18);
74 this._AutogenKey = true;
75 }
76 else
77 ...{
78 this._AutogenKey = false;
79 if (text2.Length == 0x30)
80 ...{
81 TripleDESCryptoServiceProvider provider1 = null;
82 try
83 ...{
84 provider1 = new TripleDESCryptoServiceProvider();
85 }
86 catch (Exception)
87 ...{
88 }
89 if (provider1 == null)
90 ...{
91 throw new ConfigurationException(HttpRuntime.FormatResourceString("cannot_use_Triple_DES"), node2);
92 }
93 }
94 if ((text2.Length != 0x30) && (text2.Length != 0x10))
95 ...{
96 throw new ConfigurationException(HttpRuntime.FormatResourceString("Unable_to_get_cookie_authentication_decryption_key", text2.Length.ToString()), node2);
97 }
98 this._DecryptionKey = MachineKey.HexStringToByteArray(text2);
99 if (this._DecryptionKey == null)
100 ...{
101 throw new ConfigurationException(HttpRuntime.FormatResourceString("Invalid_decryption_key"), node2);
102 }
103 }
104 if (flag2)
105 ...{
106 int num3 = SymbolHashCodeProvider.Default.GetHashCode(HttpContext.Current.Request.ApplicationPath);
107 this._DecryptionKey[0] = (byte) (num3 & 0xff);
108 this._DecryptionKey[1] = (byte) ((num3 & 65280) >> 8);
109 this._DecryptionKey[2] = (byte) ((num3 & 16711680) >> 0x10);
110 this._DecryptionKey[3] = (byte) ((num3 & 4278190080) >> 0x18);
111 }
112 }
113}
114
这段程序从配置文件中分析了validationKey和decryptionKey,他们两个的处理过程很相似。第16行node1是validationKey,第28行开始对其进行分析。第31行bool flag1 = text1.EndsWith(",IsolateApps");用flag1表示是否有",IsolateApps"。下面从36至52行都没用到flag1。53-60是关键。看到这里就明白了,MSDN中所说的“application's application ID”其实就是HttpContext.Current.Request.ApplicationPath,不同的ApplicationPath所生成的validationKey和decryptionKey就不一样。
因为我上面做实验是用的两个不同的WebSite,当然也是不同的WebApplication,但由于两个WebApplication的ApplicationPath都是"/",所以它们的validationKey和decryptionKey是相同的!要是在相同的WebSite建立不同的虚拟目录并配置成应用程序,所生成的Cookie就不通用了。我也做了实验,证实了这一点,步骤就不用说了。
这也验证了我在《ASP.NET虚拟主机中Forms Authentication的安全性》中得到的结论,在虚拟主机环境中使用表单验证时很容易被同一台电脑上的其它WebApplication所欺骗。虽然validationKey和decryptionKey还可以在web.config中自定义,可是有多少虚拟主机提供商对不同的WebApplication用了不同的用户运行,并且对每个站点目录配置了恰当的NTFS权限呢?
测试环境:Windows 2003, .NET Framework 1.1 sp1(--2005-7-27补充)