導言:
ASP.NET應用程序的設置信息通常都存儲在一個名為Web.config的XML文件裡。在教程的前面部分我們已經好幾次修改過Web.config文件了.比如在第一章,我們創建名為Northwind的數據集時,數據庫連接字符串信息自動的添加到Web.config文件的<connectionStrings>節點.再後來,在第3章裡,我們手動更新了Web.config文件,添加了一個<pages>元素,對所有的ASP.NET頁面運用DataWebControls主題.
由於Web.config文件包含了敏感的信息,比如連接字符串.所以確保Web.config文件內容的安全性是很重要的,對未經授權的訪問者應隱藏這些敏感信息.默認情況下,對.config後綴名的文件的任何HTTP請求都由ASP.NET引擎來處理,它將返回“This type of page is not served”的信息,如圖1所示.這意味著訪問者無法通過在其浏覽器的地址欄鍵入‘http://www.YourServer.com/Web.config'來訪問你的Web.config文件.
圖1:通過浏覽器訪問Web.config將返回“This type of page is not served”的信息
但是如果某個攻擊者找到其它方法來訪問你的Web.config文件的內容又怎麼辦呢?他會做怎樣的修改?我們又采取怎樣的步驟來保護Web.config文件的這些信息呢?幸運的是,Web.config文件的絕大多數節點並不包含敏感信息.如果攻擊者知道你的ASP.NET頁面使用的默認的主題的名字又會搞哪些破壞呢?
Web.config文件的某些節點包含了敏感信息,比如:connection strings, user names, passwords, server names, encryption keys等等.我們能在下面的這些節點找到這些信息:
.<appSettings> .<connectionStrings> .<identity> .<sessionState>
在本文我們將考察保護這些敏感信息的技術.就像我們將看到的那樣,.NET Framework 2.0版本包含了一個保護配置系統,我們可以使用它很容易地對選定的配置節點進行加密和解密.
注意:在本文結尾部分,我們將看到微軟對從一個ASP.NET應用程序連接到數據庫時的建議.除了對連接字符串進行加密外,我們還可以連接到一個處於“安全模式”的數據庫使你的系統更強大.
第一步:考察ASP.NET 2.0的保護配置選項
ASP.NET 2.0包含一個保護配置系統以對配置信息進行加密和解密.這些方法包含在.NET Framework,可用來編程加密和解密配置信息.該保護配置系統使用provider model模式.它允許開發者選擇執行哪種加密.
.NET Framework包含了2種protected configuration providers:
.RSAProtectedConfigurationProvider :加密和解密時使用不對稱RSA運算法則(RSA algorithm)
.DPAPIProtectedConfigurationProvider:加密和解密時使用Windows Data Protection API (DPAPI)
由於保護配置系統執行的是provider design模式,因此我們可以創建自己的protected configuration provider並運用到自己的程序裡.具體過程可參閱文章《Implementing a Protected Configuration Provider》(http://msdn2.microsoft.com/en-us/library/wfc2t3az(VS.80).aspx)
RSA providers 和 DPAPI providers在加密和解密時使用“密匙”(keys),這些“密匙”可以存儲在“機器級”(Machine-level)和“用戶級”(user-level).機器級密匙在這種情況下很理想:每個web應用程序都運行在自己專有的服務器上,或某個服務器上的多個應用程序共享同樣的加密信息.而用戶級密匙在共享服務器環境裡是比較理想的安全選擇.此時,同服務器上的其它程序不能對你加密的配置信息進行解密.
本教程的示例將使用DPAPI provider和機器級密匙.具體來說,我們將對Web.config文件裡的<connectionStrings>節點進行加密.對RSA provider以及用戶級密匙的更多信息請參考本文結束部分的外延閱讀資料.
注意:RSAProtectedConfigurationProvider 和DPAPIProtectedConfigurationProvider providers在machine.config文件裡被分別組冊成RsaProtectedConfigurationProvider 和DataProtectionConfigurationProvider。當我們對配置信息進行加密或解密時我們需要提供相應的provider名稱(即RsaProtectedConfigurationProvider 或 DataProtectionConfigurationProvider);而不是實際的類型名(即RSAProtectedConfigurationProvider 和 DPAPIProtectedConfigurationProvider). 你可以在$WINDOWS$/Microsoft.NET/Framework/version/CONFIG文件夾裡找到machine.config文件.
第二步:通過編程加密和解密配置節點
使用某個provider,我們只需要很少的幾行代碼就可以對某個配置節點加密或解密.這些代碼僅僅需要引用相應的配置節點,調用其ProtectSection 或 UnprotectSection方法,再調用Save方法來執行.另外,.NET Framework包含了一個很有用的命令行功能來進行加密和解密,我們將在第3步考察該功能.
為了便於演示,我們需要創建一個包含按鈕的ASP.NET頁面,以便於對Web.config文件的<connectionStrings>節點進行加密和解密.
打開AdvancedDAL文件夾裡的EncryptingConfigSections.aspx頁面,拖一個TextBox控件到頁面,設其ID為WebConfigContents;TextMode屬性為MultiLine;Width和Rows屬性分別為95% 和 15.該TextBox控件用於顯示Web.config文件的內容,以查看其內容是否已經加密了.當然,在現實程序裡,我們不可能將Web.config文件的內容顯示出來.
在該TextBox控件下面添加2個Button控件,ID分別為EncryptConnStrings 和 DecryptConnStrings;設其Text屬性為“Encrypt Connection Strings” 和 “Decrypt Connection Strings”.
此時你的界面看起來和下面的差不多:
圖2:在頁面上添加一個TextBox控件和2個Button控件
接下來,在頁面初次登錄時我們需要在ID為WebConfigContents的TextBox控件裡將Web.config文件的內容顯示出來。在頁面的後台類裡添加如下的代碼,該代碼添加了一個名為DisplayWebConfig的方法,在Page_Load事件處理器裡,當Page.IsPostBack 為 false時便調用該方法:
protected void Page_Load(object sender, EventArgs e) { // On the first page visit, call DisplayWebConfig method if (!Page.IsPostBack) DisplayWebConfig(); } private void DisplayWebConfig() { // Reads in the contents of Web.config and displays them in the TextBox StreamReader webConfigStream = File.OpenText(Path.Combine(Request.PhysicalApplicationPath, "Web.config")); string configContents = webConfigStream.ReadToEnd(); webConfigStream.Close(); WebConfigContents.Text = configContents; }
該DisplayWebConfig方法調用File class類來打開應用程序的Web.config文件;調用StreamReader class類將內容讀入一個字符串;再調用Path class類來獲取Web.config文件的物理地址.這3個類都位於System.IO命名空間.所以我們應該在後台類的頂部添加using System.IO聲明,又或者在這些類的前面添加“System.IO.”前綴.
接下來,我們需要為這2個按鈕的Click事件添加事件處理器,在一個DPAPI provider裡使用機器級密匙對<connectionStrings>節點進行加密和解密.在設計器裡,雙擊這2個按鈕以添加Click事件處理器,添加如下代碼:
protected void EncryptConnStrings_Click(object sender, EventArgs e) { // Get configuration information about Web.config Configuration config = WebConfigurationManager.OpenWebConfiguration(Request.ApplicationPath); // Let's work with the <connectionStrings> section ConfigurationSection connectionStrings = config.GetSection("connectionStrings"); if (connectionStrings != null) // Only encrypt the section if it is not already protected if (!connectionStrings.SectionInformation.IsProtected) { // Encrypt the <connectionStrings> section using the // DataProtectionConfigurationProvider provider connectionStrings.SectionInformation.ProtectSection( "DataProtectionConfigurationProvider"); config.Save(); // Refresh the Web.config display DisplayWebConfig(); } } protected void DecryptConnStrings_Click(object sender, EventArgs e) { // Get configuration information about Web.config Configuration config = WebConfigurationManager.OpenWebConfiguration(Request.ApplicationPath); // Let's work with the <connectionStrings> section ConfigurationSection connectionStrings = config.GetSection("connectionStrings"); if (connectionStrings != null) // Only decrypt the section if it is protected if (connectionStrings.SectionInformation.IsProtected) { // Decrypt the <connectionStrings> section connectionStrings.SectionInformation.UnprotectSection(); config.Save(); // Refresh the Web.config display DisplayWebConfig(); } }
這2個按鈕的事件處理器的代碼幾乎是一樣的.它們一開始都通過WebConfigurationManager class類的OpenWebConfiguration方法獲取當前應用程序的Web.config文件的信息. 該方法根據指定的有效路徑返回web配置文件。接下來,再通過Configuration class類的GetSection(sectionName)方法訪問Web.config文件的<connectionStrings>節點.該方法返回一個ConfigurationSection對象.
該ConfigurationSection對象包含了一個SectionInformation屬性,用來闡述加密節點的其它相關信息. 就像上面的代碼顯示的那樣,我們通過查看SectionInformation的IsProtected屬性來判斷是否對配置節點進行了加密.此外,還可以通過SectionInformation的ProtectSection(provider) 和 UnprotectSection方法對節點進行加密或解密.
ProtectSection(provider)方法有一個字符串類型的輸入參數,該參數指定了用來加密的protected configuration provider的名稱。在EncryptConnString按鈕的事件處理器裡,我們將“DataProtectionConfigurationProvider”傳遞給ProtectSection(provider)方法,因此指明了用到的是DPAPI provider.而UnprotectSection方法可以確定加密時用到的provider,因此不需要任何的輸入參數.
調用ProtectSection(provider) 或 UnprotectSection方法後,我還必須調用Configuration對象的Save method方法來進行具體的操作. 一旦完成加密或解密並保存後,我們調用DisplayWebConfig方法將更新後的Web.config文件的內容上傳到TextBox控件.
鍵入上述代碼後,在浏覽器裡測試EncryptingConfigSections.aspx頁面,最開始你將看到頁面將Web.config文件的<connectionStrings>節點的內容以純文本的形式展示出來.
圖3:顯示<connectionStrings>節點的內容
現在,點擊“Encrypt Connection Strings”按鈕,如果“請求確認”(request validation)處於激活狀態的話,回傳頁面時將拋出一個HttpRequestValidationException異常,顯示一個消息:“A potentially dangerous Request.Form value was detected from the client.”。這個Request validation,在ASP.NET 2.0裡默認為處於激活狀態,禁止服務器接受含有未編碼的HTML的內容,它被設計來保護服務器免受注入式腳本的攻擊.可以從頁面或應用程序來禁止該功能.我們在該頁禁用它,在頁面聲明代碼的頂部的的@Page標記裡ValidateRequest設置為false,如下:
<%@ Page ValidateRequest="False" ... %>
在禁用該功能後,再次點擊“Encrypt Connection Strings”按鈕,頁面回傳後就可以訪問配置文件了,並用DPAPI provider對<connectionStrings>節點進行加密. TextBox控件然後將Web.config文件更新後的內容顯示出來,如圖4所示,<connectionStrings>節點的信息現在已經被加密了.
圖4:點擊“Encrypt Connection Strings”按鈕對<connectionString>節點進行加密
在加密前,我暫時地將<CipherData>元素裡的內容轉移了:
<connectionStrings configProtectionProvider="DataProtectionConfigurationProvider"> <EncryptedData> <CipherData> <CipherValue>AQAAANCMnd8BFdERjHoAwE/...zChw==</CipherValue> </CipherData> </EncryptedData> </connectionStrings>
注意:<connectionStrings>元素指定了用來加密的provider(即DataProtectionConfigurationProvider).當點擊“Decrypt Connection Strings”按鈕時UnprotectSection方法將會用到該信息.對於加密的連接字符串,系統可以自動的對其解密.簡而言之,我們不需要再對加密的<connectionString>節點添加任何其它的代碼。我們來做個驗證,打開以前的教程,比如(~/BasicReporting/SimpleDisplay.aspx頁面),如圖5所示,頁面像我們期望的那樣工作正常,這就表明了經過加密的連接字符串被ASP.NET頁面自動解密了.
圖5:數據訪問層自動解密連接字符串信息
為將加密的<connectionStrings>節點恢復到純文本樣式,點擊“Decrypt Connection Strings”按鈕。頁面回傳後,你將看到Web.config文件裡的連接字符串恢復到純文本樣式.此時,屏幕開起來像是最初登錄的樣子(見圖3)
第三步:用aspnet_regiis.exe對配置節點進行加密
.NET Framework包含了很多的命令行工具,可以在$WINDOWS$/Microsoft.NET/Framework/version/ folder文件夾裡找到這些工具.以第59章《使用SQL緩存依賴項SqlCacheDependency 》為例,我們用aspnet_regsql.exe命令行工具為SQL緩存依賴添加裡必要的體系結構.該文件夾裡的另一個有用的工具是ASP.NET IIS Registration tool (aspnet_regiis.exe). 就像其名字暗示的那樣,這個ASP.NET IIS Registration工具主要用來在微軟專業Web server,IIS上注冊ASP.NET 2.0應用程序.
除了其與IIS相關的屬性外,該ASP.NET IIS Registration工具也可以對Web.config文件的配置節點進行加密和解密. 下面的是使用aspnet_regiis.exe命令行工具對配置節點加密的常規代碼:
aspnet_regiis.exe -pef section physical_directory -prov provider
其中section是要加密的配置節點(比如“connectionStrings”);physical_directory 為web應用程序根節點的完整物理路徑;provider是用到的protected configuration provider的名稱(比如“DataProtectionConfigurationProvider”). 另外,如果你將web應用程序在IIS裡進行了注冊了的話,你就可以用相當路徑來代替絕對路徑:
aspnet_regiis.exe -pe section -app virtual_directory -prov provider
下面為使用aspnet_regiis.exe的例子,它用DPAPI provider,機器級密匙,對<connectionStrings>節點進行加密:
aspnet_regiis.exe -pef
"connectionStrings" "C:/Websites/ASPNET_Data_Tutorial_73_CS"
-prov "DataProtectionConfigurationProvider"
類似的,該aspnet_regiis.exe命令行工具也可以用來解密配置節點,不過我們要將-pef替換成-pdf或-pd。當然,解密時不需要指定provider名稱.
aspnet_regiis.exe -pdf section physical_directory -- or -- aspnet_regiis.exe -pd section -app virtual_directory
注意:由於我們使用的是DPAPI provider,它使用的密匙是又電腦指定的,所以你必須在存儲web頁面的同一台電腦上運行aspnet_regiis.exe工具. 比如,你在本地電腦上運行這個命令行,然後又將加了密的連接字符串上載到另一個服務器上,該服務器就無法對其進行解密,因為加密的密匙是在本地電腦上指定的.如果是使用RSA provider的話就不存在這種局限性,因為RSA provider可以將密匙(RSA keys)傳遞給另一台電腦.
理解Database Authentication Options
在任何應用程序向Microsoft SQL Server數據庫發出SELECT,INSERT,UPDATE,或DELETE請求之前,數據庫首先要確定請求者的身份.該過程可分為2種驗證模式:authentication 和 SQL Server provides:
.Windows Authentication:在Visual Studio 2005的ASP.NET Development Server裡運行一個ASP.NET應用程序時,ASP.NET應用程序假定身份(identity)為當前登錄用戶。而如果運行在Microsoft Internet Information Server (IIS)上的話,ASP.NET應用程序假定身份(identity)為domainName/MachineName or domainName/NETWORK SERVICE,,雖然這些都可以用戶定制.
.SQL Authentication:驗證的時候需要提供用戶ID和password,使用SQL authentication的話,可以由連接字符串來提供ID和password.
一般使用Windows authentication模式,因為其更安全.在Windows authentication模式下,連接字符串不需要用戶名和密碼,並且如果web服務器和數據庫服務器分屬不同的電腦的話,(credentials)認證在網絡間傳輸時並不以純文本格式傳輸.而如果是SQL authentication模式的話,將對連接字符串進行硬編碼,且認證在web服務器和數據庫服務器之間以純文本格式進行傳輸.
本教程使用的是Windows authentication.我們可以通過連接字符串來查看到底使用的是哪種認證。本教程的Web.config文件的連接字符串如下:
Data Source=./SQLEXPRESS; AttachDbFilename=|DataDirectory|/NORTHWND.MDF; Integrated Security=True; User Instance=True
術語“Integrated Security=True”,以及缺少用戶名和密碼都表明我們使用的是Windows authentication模式。不過在一些連接字符串裡用術語“Trusted Connection=Yes” 或 “Integrated Security=SSPI”來替換“Integrated Security=True”, 不過它們都表明使用的是Windows authentication.
下面的代碼顯示使用的是SQL authentication:
Server=serverName; Database=Northwind; uid=userID; pwd=password
假想某個攻擊者可以查看你的應用程序的Web.config文件。如果你使用的是SQL authentication模式通過Internet連接到數據庫,攻擊者可以利用連接字符串通過SQL Management Studio或他自己網站上的ASP.NET頁面連接到你的數據庫.為降低風險,我們需要對Web.config文件的連接字符串進行加密.
注意:關於SQL Server裡不同認證模式的更多信息應參閱文章《Building Secure ASP.NET Applications: Authentication, Authorization, and Secure Communication》(http://msdn2.microsoft.com/en-us/library/aa302392.aspx);關於Windows 和 SQL authentication不同之處的更多示例,應參閱ConnectionStrings.com網站.
結語:
默認情況下,ASP.NET應用程序裡的所有以.config為後綴的文件都不能通過浏覽器訪問.這是因為這些文件可能包含了一些敏感信息,比如:數據庫連接字符串、用戶名和密碼等。 .NET 2.0包含的保護配置系統可以通過對指定的配置節點進行加密來加以保護.有2種內置的protected configuration providers:一個使用RSA運算法則,而另一個使用Windows Data Protection API (DPAPI).
本文考察了使用DPAPI provider來對配置信息進行加密和解密.我們可以通過編程的方式,就像在第2步探討的那樣;也可以通過使用aspnet_regiis.exe命令行工具,就像在第3步探討的那樣。關於使用RSA provider及用戶級密匙的更多信息請參考本文的外延閱讀.
祝編程快樂!
作者簡介
本系列教程作者 Scott Mitchell,著有六本ASP/ASP.NET方面的書,是4GuysFromRolla.com的創始人,自1998年以來一直應用 微軟Web技術。大家可以點擊查看全部教程《[翻譯]Scott Mitchell 的ASP.NET 2.0數據教程》,希望對大家的學習ASP.NET有所幫助。