ASP.NET MachineKey自動產生原理剖析
0 |
在ASP.NET中,MachineKey被廣泛應用於ViewState加密、Forms Authentication及Membership Cookie加密、Out-of-Process Session資料加密、Membership密碼雜湊(或加密)... 等運算。由於預設為自動產生,大部分的人(甚至開發者)不曾感受過它的存在,但它卻一直默默扮演捍衛ASP.NET安全的重要角色。比較有機會察覺到MachineKey的場合,多半出現在多台伺服器組成Web Farm的架構中,此時需手動將各伺服器的MachineKey設成一致,以避免ViewState MAC驗證失敗問題。(延伸閱讀: 保哥的文章)
以上的概念我在剛學ASP.NET時就確立了,最近在處理主機ViewState MAC驗證相關問題時,倒產生了一些新的疑問:
- 當MachineKey設為AutoGenerate時,金鑰會如何產生?
- MachineKey在什麼情況下會重新產生? (IIS Reset時會嗎?)
- 指定IsolateApps時,會如何影響所產生的MachineKey?
秉持追根究底的精神,研讀過相關文件,甚至追蹤了部分ASP.NET核心程式,理出一點頭緒:
- 當指定AutoGenerate時,MachineKey的金鑰資料來自Registry(IIS5則是存在Local Security Authority, LSA): HKEY_LOCAL_MACHINE\Software\Microsoft\ASP.NET 及HKEY_CURRENT_USER\Software\Microsoft\ASP.NET中 ,故會依ASP.NET AppPool執行身分而不同。當ASP.NET程式建立時,會先尋找HKEY_CURRENT_USER(HKCU),找不到時再找HKEY_LOCAL_MACHINE(HKLM),若HKLM也沒有,就會在HKLM下建立新的金鑰資料,若以上動作都失敗,則當場產生新的金鑰組。(參考文件: Intermittent Invalid Viewstate Error in ASP.NET Web pages)
- HttpRuntime的初始化過程會呼叫private static SetAutogenKeys(),透過webengine.dll(Unmanaged程式庫)進行上述程序取得金鑰,並將其存入private byte[] s_autogenKeys欄位。
以下是用ProcMon觀察到的證據(點選後可檢視原尺寸圖檔):
- MachineKeySection透過private void RuntimeDataInitialize(),依machine.config或web.config的machineKey的validationKey及decryptionKey Attribute決定使用自動產生金鑰或是指定金鑰。當為AutoGenerate時,會由HttpRuntime.s_autogenKeys取得金鑰值;而指定IsolateApps時,金鑰的前4個byte會由ASP.NET Web Application路徑字串的Hash值決定,亦即當多個Web Application共用AppPool時,指定IsolateApps會讓各Web Application的金鑰前四碼不同。
- MachineKeySection有個internal byte[] DecryptionKeyInternal,有些文章提及可透過它取得自動產生的解密金鑰,發現在呼叫MachineKeySection某些功能(尤其需動用金鑰的功能)後,MachineKeySection會呼叫internal void DestroyKeys()清除ValidationKey及DecryptionKey的內容(猜想是為了降低被竊取盜用的風險)。因此即便透過Reflection可讀出DecryptionKeyInternal私有屬性,在呼叫FormsAuthentication.Encrypt()或Postback(可能取了金鑰以加密ViewState)後,DecryptionKeyInternal的byte[]值就會全部變成0。(針對此一狀況,我找到改讀取private SymmetricAlgorithm s_oSymAlgoDecryption,再由其Key值取得解密金鑰的變通做法。)
既然有了理論,就該用程式碼實測驗證一番,心裡才會踏實。
以下是我設計的測試程式:
<%@ Page Language="C#" EnableViewStateMac="true" %>
<%@ Import Namespace="System.Text" %>
<%@ Import Namespace="System.Web.Configuration" %>
<%@ Import Namespace="System.Reflection" %>
<%@ Import Namespace="System.Security.Principal" %>
<%@ Import Namespace="System.Security.Cryptography" %>
<%@ Import Namespace="Microsoft.Win32" %>
<!DOCTYPE html>
<script runat="server">
void Page_Load(object sender, EventArgs e)
{
StringBuilder sb = new StringBuilder();
sb.Append("<dl>");
sb.AppendFormat("<dt>Request Url</dt><dd>{0}</dd>",
Request.Url);
sb.AppendFormat("<dt>DomainAppVirtualPath</dt><dd>{0}</dd>",
HttpRuntime.AppDomainAppVirtualPath);
//Display ASP.NET process identity information
WindowsIdentity wid = WindowsIdentity.GetCurrent();
sb.AppendFormat("<dt>AppPool Identity</dt><dd>{0}({1})</dd>",
wid.Name, wid.User.Value);
//Get current ASP.NET version
Version version = System.Environment.Version;
string aspNetVer = string.Format("{0}.{1}.{2}.0",
version.Major, version.Minor, version.Build);
//Check HKCU
string regPath = string.Format(@"Software\Microsoft\ASP.NET\{0}",
aspNetVer);
RegistryKey key = Registry.CurrentUser.OpenSubKey(regPath);
sb.AppendFormat("<dt>Registry</dt><dd>[HKEY_CURRENT_USER\\{0}]</dd>",
regPath);
//Check HKLM
regPath = string.Format(@"Software\Microsoft\ASP.NET\{0}\AutoGenKeys\{1}",
aspNetVer, wid.User.Value);
key = Registry.LocalMachine.OpenSubKey(regPath);
sb.AppendFormat("<dt>Registry</dt><dd>[HKEY_LOCAL_MACHINE\\{0}]</dd>",
regPath);
//Get machineKey settings
MachineKeySection mks = (MachineKeySection)
ConfigurationManager.GetSection("system.web/machineKey");
sb.AppendFormat("<dt>DecryptionKey Setting</dt><dd>{0}</dd>",
mks.DecryptionKey);
Type mksType = typeof(MachineKeySection);
//After using machine key to encrypt, DecryptionKeyInternal will be cleared
//Uncomment below line, you will always get 00-00-00-00
//FormsAuthentication.Encrypt(new FormsAuthenticationTicket("Jeffre", false, 60));
sb.AppendFormat("<dt>DecryptionKeyInternal</dt><dd>{0}</dd>",
BitConverter.ToString(
(byte[]) mksType.GetProperty("DecryptionKeyInternal",
BindingFlags.Instance | BindingFlags.NonPublic).GetValue(mks, null)));
SymmetricAlgorithm sa =
(SymmetricAlgorithm)mksType.GetField("s_oSymAlgoDecryption",
BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
string result = "null";
if (sa != null) result = BitConverter.ToString(sa.Key);
sb.AppendFormat("<dt>s_oSymAlgoDecryption.Key</dt><dd>{0}</dd>",
result);
//HttpRuntime.s_autogenKeys
byte[] rtAutoGenKeys = (byte[])typeof(HttpRuntime)
.GetField("s_autogenKeys", BindingFlags.NonPublic | BindingFlags.Static)
.GetValue(null);
sb.AppendFormat("<dt>HttpRuntime.s_autogenKeys</dt><dd>{0}</dd>",
BitConverter.ToString(rtAutoGenKeys));
sb.Append("</dl>");
disp.InnerHtml = sb.ToString();
}
</script>
<html>
<head runat="server">
<title>AutoGen MachineKey Test</title>
</head>
<body>
<form id="form1" runat="server">
<div id="disp" runat="server" enableviewstate="false"></div>
<asp:Button ID="bPostBack" runat="server" Text="Postback"/>
</form>
</body>
</html>
在以上程式中, 會取得ASP.NET執行身分,計算其對應金鑰的Registry路徑,並透過Reflection技巧取得DecryptionKeyInternal及oSymAlgoDecryption.Key以觀察金鑰。
【實驗1】在Windows 2003 R2執行
圖1 在Windows 2003 R2下,執行身分為NT AUTHORITY\NETWORK SERVICE,第一次執行時,DecryptionKeyInternal有值,此時s_oSymAlgoDecryption還是null
圖2 Postback後,DecryptionKeyInternal陣列值被清成0,但由s_oSymAlgoDecryption.Key可取得一模一樣的金鑰內容
【實驗2】在Windows 7 IIS7執行
圖3 在Win7下的AppPool身分為IIS APPPOOL\AspNet35(紅框中為其SID,可對應先前ProcMon所觀察到的Registry讀取路徑)
圖4 切換成另一個AppPool後,執行身分不同,自動產生的金鑰也不同
【實驗3】啟用IsolateApps
圖5 啟用IsolateApps後,金鑰的前4個byte會被修改(亦可與圖4對照)
【實驗4】驗證IsolateApps時,前4個byte內容與VirtualPath有關
- AppPool: x86, VirtualPath: /ASPNET35 –> 1F-35-E5-A1-82-26-D8-25…
- AppPool: AspNet35, VirtualPath: /ASPNET35 –> 1F-35-E5-A1-DC-4A-61-ED…
(與1.相比,前4 byte不變) - AppPool: AspNet35, VirtualPath: /ASPNET-35 –> 7C-7B-C6-9C-DC-4A-61-ED…
(與2.相比,只有前4 byte不同)
【實驗5】IISRESET後,解密金鑰並未改變
網路上有些說法是一旦AppPool被Recycle後,加密金鑰就會重新產生(參考: The encryption key is generated and used once per app pool cycle.)。但在我的實測中,不管是重啟AppPool或IISRESET,自動產生的解密金鑰內容都未發生改變。
【實驗6】<machineKey>指定自訂decryptionKey
圖6 IIS7管理介面就已內建方便的金鑰產生器
圖7 指定後,解密金鑰將等於web.config中的設定內容
【重要資安提醒】在實際環境中,ASP.NET金鑰應嚴格保密,不可外洩給任何人,否則會讓惡意人士得以偽造登入身分或擷取隱密資訊,嚴重危害網站安全。
Comments
Be the first to post a comment