在《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)。代码比较长,不过还是贴出来比较方便。
internal
MachineKeyConfig(
object
parentObject,
object
contextObject, XmlNode node)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
else42

...{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
else77

...{78
this._AutogenKey = false;79
if (text2.Length == 0x30)80

...{81
TripleDESCryptoServiceProvider provider1 = null;82
try83

...{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补充)
ASP.NET表单验证中MachineKey的安全性分析
3754





