| 本單元內容 | |
| 目標 | |
| 適用於 | |
| 如何使用本單元 | |
| 潛在威脅及因應對策 | |
| 特殊權限程式碼 | |
| 組件設計的考量因素 | |
| 類別設計的考量因素 | |
| 強式名稱 | |
| 授權 | |
| 例外管理 | |
| 檔案 I/O | |
| 事件日誌 | |
| 登錄 | |
| 資料存取 | |
| Unmanaged 程式碼 | |
| 委派 | |
| 序列化 | |
| 執行緒 | |
| 反射 | |
| 模糊化 | |
| 密碼編譯 | |
| 總結 | |
| 其他資源 |
組件是 .NET Framework 應用程式的建置組塊,也是調配、版本控制及重新使用的單位。同時也是程式碼存取安全性的信任單位 (組件中所有的程式碼都同樣地受到信任)。
本單元一開始會先列出及說明常見的組件潛在威脅和因應對策。接著是完整的安全性區域清單,您必須處理這些安全性區域,以改良組件的安全性設計和實作,其中包括了評估調配考量、遵循一致的物件導向程式設計慣例、避免程式碼因為誤用而出錯、確定不會向呼叫者顯露內部系統層級資訊,以及限制可以呼叫程式碼的人員。
透過本單元即可:
| • | 使用簡單、驗證過的編碼技術改善組件的安全性。 |
| • | 利用設計良好的介面和穩固的物件導向程式設計技術,降低遭受攻擊的風險。 |
| • | 使用強式名稱,提高組件的安全性。 |
| • | 降低呼叫 Unmanaged 程式碼的相關風險。 |
| • | 撰寫安全的資源存取程式碼,包括檔案 I/O、登錄、事件日誌、資料庫及網路存取程式碼。 |
| • | 知道可以使用哪些因應對策,以解決常見的組件威脅,包括權限提升、程式碼注入、資訊洩露及竄改。 |
本單元適用於下列產品及技術:
| • | Microsoft Windows Server 2000 及 2003 |
| • | Microsoft .NET Framework 1.1 及 ASP.NET 1.1 |
若要充分瞭解本單元:
| • | 請將本單元與單元 8<程式碼存取安全性實務>搭配使用。單元 8 將告訴您,如何使用程式碼存取安全性功能,進一步增強組件的安全性。 |
| • | 請使用對應的檢查清單。如需簡述這兩個單元之最佳實務與建議的檢查清單,請參閱本指南<檢查清單>一節中的<檢查清單:Managed 程式碼的安全性檢閱>。 |
您可能發現與
.NET Framework 1.0 和 1.1 版相關的下列問題:
| • | 由於需要完全信任呼叫者的核心 .NET 組件數量,所以很難建立部分信任的環境。 |
| • | ASP.NET 網頁無法使用強式名稱。 |
| • | 將組件發行到全域組件快取是用於沙箱 (Sandbox) 程式碼的唯一解決方案。 |
| • | 若要在組件中使用強式名稱,並且發行到全域組件快取,必須具有足夠的權限,才能在 Web 伺服器上執行命令,例如 Sn.exe 和 Gacutil.exe。 |
| • | Authenticode 舉證並非由 ASP.NET 主機載入,因此您用來建立 ASP.NET Web 應用程式的安全性原則。 |
認識潛在威脅和常見的攻擊類型,有助於找出適當的因應對策,並且可以建立更安全且強固的組件。主要的威脅如下:
| • | 未授權的存取或權限提升,或兩者皆有 |
| • | 程式碼注入 |
| • | 資訊洩露 |
| • | 竄改 |
[圖 7.1] 說明這些主要威脅。

[圖 7.1]
組件層級的威脅
因未授權的存取而產生的風險,可能造成權限提升,使未授權的使用者或未授權的程式碼可以呼叫您的組件,並執行特殊權限操作及存取限制的資源。
弱點
可能造成未授權存取和權限提升的弱點包括:
| • | 缺少以角色為基礎或不充足的授權 |
| • | 不慎暴露內部類型和類型成員 |
| • | 不安全地使用程式碼存取安全性判斷提示和連結需求 |
| • | 未密封和未設限制的基底類別,允許任何程式碼衍生出程式碼 |
攻擊
常見的攻擊包括:
| • | 引誘攻擊,惡意程式碼透過信任的中繼組件存取您的組件,以避開授權機制 |
| • | 惡意程式碼藉由直接呼叫未構成組件之公開 API 的類別,避開存取控制 |
因應對策
用於防止未授權存取和權限提升的因應對策包括:
| • | 使用以角色為基礎的授權,在所有公開類別和類別成員中實施存取控制。 |
| • | 限制類型和成員的能見度,以限制能夠公開存取的程式碼。 |
| • | 利用適當的權限要求,以沙箱隔離權限程式碼,並確定呼叫程式碼已經加以授權。 |
| • | 以程式碼存取安全性將非基底類別密封或限制繼承。 |
如果遭到程式碼注入攻擊,攻擊者就會使用組件的處理序層級安全性內容執行任何程式碼。如果您的組件呼叫 Unmanaged 程式碼,並且組件是在特殊權限帳戶下執行,那麼風險就會增加。
弱點
可能導致程式碼注入的弱點包括:
| • | 輸入驗證不周全,尤其是組件對 Unmanaged 程式碼發出呼叫時 |
| • | 接受部分信任程式碼的委派 |
| • | 權限過高的處理序帳戶 |
攻擊
常見的程式碼注入攻擊包括:
| • | 緩衝區溢位 |
| • | 從不受信任的來源叫用委派 |
因應對策
用於防止程式碼注入的因應對策包括:
| • | 驗證輸入參數。 |
| • | 驗證傳遞到 Unmanaged API 的資料。 |
| • | 不要接受不受信任來源的委派。 |
| • | 使用增強式型別委派,並在呼叫委派之前拒絕使用權限。 |
| • | 若要進一步降低風險,請使用最小權限帳戶執行組件。 |
不論組件是否向合法和惡意的使用洩露機密資料 (例如,例外詳細資訊和純文字機密資訊),都會遇到資訊洩露的問題。將組件的 Microsoft Intermediate Language (MSIL) 反向工程為原始碼,會比使用二進位機器碼更容易。這會成為智慧財產的威脅。
弱點
可能導致資訊洩露的弱點包括:
| • | 不當或不正常的例外處理 |
| • | 以硬式編碼方式在程式碼中寫入機密資訊 |
攻擊
常見的攻擊包括:
| • | 傳遞錯誤格式的輸入到組件,試圖造成錯誤 |
| • | 使用組件上的 ILDASM 來竊取機密資訊 |
因應對策
用於防止資訊洩露的因應對策包括:
| • | 穩固的輸入驗證 |
| • | 結構化例外處理 (Structured Exception Handling),並將一般錯誤傳回給用戶端 |
| • | 不要在程式碼中儲存機密資訊 |
| • | 使用模糊化工具,以擾亂解編程式並保護智慧財產 |
因竄改而產生的風險,攻擊者可能會改變二進位 DLL 或 EXE 組件檔案中的 MSIL 指示,以修改您的組件。
弱點
使組件容易遭到竄改的主要弱點,在於缺少強式名稱簽章。
攻擊
常見的攻擊包括:
| • | 直接操作 MSIL 指示 |
| • | 對 MSIL 指示進行反向工程 |
因應對策
若要反擊竄改的威脅,請使用強式名稱,以私密金鑰簽署組件。載入已簽署組件時,公用語言執行時間會偵測組件是否已經以任何方式修改,如果已經改變了,將不會載入組件。
當您設計並建立安全組件時,可以識別特殊權限程式碼。這對程式碼存取安全性而言非常地重要。特殊權限程式碼屬於 Managed 程式碼,會存取保全資源或執行其他安全性機密操作,例如呼叫 Unmanaged 程式碼、使用序列化或使用反射。之所以稱為特殊權限程式碼,是因為特殊權限程式碼必須經由程式碼存取安全性原則授予使用權限,才能作用。非特殊權限程式碼只需要執行的使用權限。
程式碼需要程式碼存取安全性權限的資源類型,包括了檔案系統、資料庫、登錄、事件日誌、Web 服務、通訊端、DNS 資料庫、目錄服務及環境變數。
程式碼需要程式碼存取安全性權限的其他特殊權限操作,包括了呼叫 Unmanaged 程式碼、使用序列化、使用反射、建立及控制應用程式網域、建立主體物件及操作安全性原則。
如需有關存取資源及執行特殊權限操作所需之程式碼存取安全性權限特定類型的詳細資訊,請參閱單元 8<程式碼存取安全性實務>中的<特殊權限程式碼>。
設計時所要考量的最重要問題之一,是組件目標環境的信任等級,因為這會影響授予程式碼及呼叫您程式碼之程式碼的存取安全性權限。這是由系統管理員所定義的程式碼存取安全性原則而決定,並且會影響您的程式碼允許可以存取的資源類型,以及能夠執行的其他特殊權限操作。
定義您的組件時,應該:
| • | 識別特殊權限程式碼 |
| • | 識別目標環境的信任等級 |
| • | 利用沙箱隔離高特殊權限程式碼 |
| • | 設計公用介面 |
識別存取安全性資源或執行安全性機密操作的程式碼。這種程式碼需要特定程式碼存取安全性權限才能運作。
識別您的組件需要存取的資源類型,當您的組件最後所要執行的環境中並未授予相關的程式碼存取安全性權限時,您就可以識別可能發生的潛在問題。在此情況下,如果系統管理員允許,您必須更新應用程式的程式碼存取安全性原則,或利用沙箱隔離您的特殊權限程式碼。如需沙箱的詳細資訊,請參閱單元 9<配合 ASP.NET 使用程式碼存取安全性>。
此外,請識別您的組件必須執行的任何特殊權限操作,如此您就可以知道在執行時間,您的程式碼所需要的程式碼存取權限。
您的組件所要安裝的目標環境很重要,因為程式碼存取安全性原則可能會限制您的組件允許執行的作業。例如,如果您的組件必須使用 OLE DB,將會處於沒有完全信任的環境中。
完全信任表示程式碼擁有不受限制的程式碼存取安全性權限,因此程式碼能夠在作業系統安全性的範圍內,存取所有的資源類型,並且執行特殊權限操作。完全信任環境是 Web 應用程式的預設環境,並支援安裝在 Web 伺服器上的組件,不過您可以設定應用程式的 <trust> 元素加以改變。
部分信任環境是完全信任以外的任何環境。.NET Framework 具有許多預先定義的信任等級,您可以直接使用或自訂這些等級,以符合您的特定安全性要求。信任等級也可以由程式碼的來源決定。例如,網路共用上的程式碼和本機電腦上的程式碼比起來,就比較不受信任,因此執行特殊權限操作的能力也就有所限制。
支援部分信任呼叫者
如果您的組件支援部分信任呼叫者 (就是您並未完全信任的程式碼),那麼安全性危害的風險就會大增。程式碼存取安全性具有其他的保護,可以減輕風險。如需適用於支援部分信任呼叫者之組件的其他指導方針,請參閱單元 8<程式碼存取安全性實務>。不使用其他程式設計時,您的程式碼在下列兩種情況下會支援部分信任呼叫者:
| • | 您的組件沒有強式名稱。 |
| • | 您的組件有強式名稱,並且包含 AllowPartiallyTrustedCallersAttribute (APTCA) 組件層級屬性。 |
您的組件執行的信任環境之所以重要,其原因如下:
| • | 根據程式碼存取安全性原則授予的程式碼存取安全性權限,部分信任組件只能存取受限制的資源,以及執行限制的操作。 | ||||
| • | 部分信任組件必須包含 AllowPartiallyTrustedCallersAttribute,否則不能呼叫強式名稱組件。 | ||||
| • | 其他部分信任組件可能無法呼叫您的組件,因為它們沒有必要的權限。呼叫組件要呼叫您的組件時,必須具有的權限取決於:
|
如果要避免只為符合執行特殊權限操作之少數方法的需求,而授予整個應用程式強大的權限,您可以利用沙箱隔離特殊權限程式碼,並且放在另一個組件中。這讓系統管理員可以設定程式碼存取安全性原則,以授予特定組件中程式碼延伸的權限,而非授予整個應用程式。
例如,如果您的應用程式需要呼叫 Unmanaged 程式碼,請將 Unmanaged 呼叫隔離在包裝函式組件中,讓系統管理員能夠授予包裝函式組件 UnmanagedCodePermission ,而非授予整個應用程式。
注意 沙箱隔離必須使用不同的組件,並維護安全性權限,以防止發生完整的堆疊查核行程 (Stack Walk)。
如需沙箱隔離 Unmanaged API 呼叫的詳細資訊,請參閱單元 8<程式碼存取安全性實務>。
仔細思考哪些類型和成員構成您組件的公用介面。減少進入點的數目,並使用設計良好的最小公用介面,以降低遭受攻擊的可能性。
除了使用設計良好和最小的公用介面之外,也可以設計安全的類別,使組件遭到攻擊的可能性降至更低。安全的類別符合穩定的物件導向設計原則、防止不必要的繼承、限制可以呼叫類別的對象和程式碼。下列建議可以幫助您設計安全的類別:
| • | 限制類別和成員的能見度 |
| • | 將非基底類別密封 |
| • | 限制哪些使用者可以呼叫您的程式碼 |
| • | 使用屬性顯露欄位 |
只針對構成組件之公用介面的類型與成員使用 public 存取修飾詞 (Modifier)。這可以立即減少被攻擊的風險,因為組件外面的程式碼只能存取公用類型。儘可能地限制其他所有類型與成員,並且盡量使用 private 存取修飾詞。只有在衍生的類別能夠存取成員時,才能使用 protected ,並且只在同一組件中的其他類別能夠存取成員時,才能使用 internal 。
注意 C# 也允許您結合 protected 和 internal 以建立 protected internal 成員,限制只能存取目前的組件或繼承類型。
如果類別並未設計成基底類別,請使用 sealed 關鍵字防止繼承,如以下程式碼範例所示。
public sealed class NobodyDerivesFromMe
{}
針對基底類別,可以使用程式碼存取安全性繼承要求,限制允許其他哪些程式碼從您的類別衍生。如需詳細資訊,請參閱單元 8<程式碼存取安全性實務>。
以宣告式主要使用權限要求附註類別與方法,以控制哪些使用者可以呼叫您的類別和類別成員。在下面的範例中,只有指定 Windows 群組的成員可以存取 Orders 類別。像這樣的類別層級屬性會套用至所有的類別成員,也可以在個別方法使用宣告式主要使用權限要求。方法層級屬性會覆寫類別層級屬性。
[PrincipalPermission(SecurityAction.Demand,
Role=@"DomainName\WindowsGroup")]
public sealed class Orders()
{
}
將所有欄位設定為 private。若要讓外部類型能夠存取欄位值,請使用唯讀或讀取/寫入屬性。屬性可以讓您加入額外的限制,例如輸入驗證或權限要求,如以下程式碼範例所示。
public sealed class MyClass
{
private string field; // field is private
// Only members of the specified group are able to
// access this public property
[PrincipalPermission(SecurityAction.Demand,
Role=@"DomainName\WindowsGroup")]
public string Field
{
get {
return field;
}
}
}
組件的強式名稱包含文字、版本編號、選擇性文化特性、公開金鑰 (通常代表您的開發組織),以及數位簽章。您可以檢查 Machine.config 的內容,並且瞭解如何參照強式名稱的組件,以查看強式名稱的各種元件。
以下範例顯示 Machine.config 中如何參照 System.Web 組件。這個範例中,assembly 屬性顯示文字名稱、版本、文化特性及公開金鑰語彙基元,這是公開金鑰的縮短形式。
<add assembly="System.Web, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
您可以依照所要的組件使用方式,來決定是否使用強式名稱。在組件加入強式名稱的主要原因包括:
| • | 您想要確保部分信任程式碼無法呼叫您的組件。
如需 APTCA 的詳細資訊,請參閱單元 8<程式碼存取安全性實務>。 |
| • | 組件是設計用於在許多應用程式間共用。
|
| • | 您想要使用強式名稱做為安全性舉證。
注意 ASP.NET 主機不會載入 Authenticode 舉證,也就是說,無法用來建立 ASP.NET Web 應用程式的安全性原則。 如需舉證類型和程式碼存取安全性的詳細資訊,請參閱單元 8<程式碼存取安全性實務>。 |
強式名稱除了版本的優點之外,還提供許多安全性優點:
| • | 強式名稱組件使用數位簽章加以簽名。這會保護組件不會遭到修改。任何竄改都會造成在組件載入時間進行的驗證處理程序失敗。將會產生一個例外,而且不會載入組件。 |
| • | 除非您特別加入 AllowPartiallyTrustedCallersAttribute (APTCA),否則部分信任程式碼無法呼叫強式名稱組件。 注意 如果使用 APTCA,一定要閱讀單元 8<程式碼存取安全性實務>,以取得進一步改良組件安全性的其他指導方針。 |
| • | 強式名稱提供程式碼存取安全性原則評估的密碼編譯增強式舉證。這可以讓系統管理員授予特定組件使用權限,並且也允許開發人員使用 StrongNameIdentityPermission ,限制哪個程式碼可以呼叫 Public 成員,或是從未密封的類別衍生。 |
.NET Framework 包含 Sn.exe 公用程式,可以協助您為組件加上強式名稱。不需要 X.509 憑證,就能將強式名稱加入組件。
| • | 為組件加上強式名稱
|
在應用程式開發過程中,延遲為組件簽名是很好的安全性做法。這會使公開金鑰放在組件中,也就能當成舉證供程式碼存取安全性原則使用,但是組件並未簽名,所以還不能防止竄改。就安全性的觀點而言,延遲簽名有兩個主要優點:
| • | 用於為組件簽名及建立組件數位簽章的私密金鑰會安全地保存在中央位置。只有少數受信任的人員才能存取金鑰。因此,私密金鑰被盜用的機會就大為減少。 |
| • | 程式開發小組的所有成員,使用可以代表開發組織或軟體發行者的單一公開金鑰,而非每位開發人員使用自己的公開、私密金鑰組 (通常是由 sn k 命令產生)。 |
| • | 若要建立延遲簽名的公開金鑰檔案 這個程序由簽署授權執行,以建立公開金鑰檔案,開發人員可以用來延遲組件的簽名。
|
| • | 若要延遲組件的簽名 這個程序由開發人員執行。
|
在本文撰寫時,ASP.NET 網頁組件還無法使用強式名稱,因為它是動態編譯的。即使您使用程式碼後置 (Code-Behind) 的檔案,建立包含網頁類別執行碼的預先編譯組件,ASP.NET 還是會動態建立及編譯包含網頁視覺元素的類別。這個類別從您的網頁類別衍生,這也表示您無法使用強式名稱。
注意 可以為網頁程式碼呼叫的其他任何組件加上強式名稱,例如,包含資源存取、資料存取或商務邏輯碼的組件,但是組件必須放在全域組件快取中。
為部分信任設定之 ASP.NET Web 應用程式所呼叫的任何強式名稱組件必須安裝在全域組件快取中。這是因為 ASP.NET 主機會將所有強式名稱組件當成無定義域載入。
ASP.NET 處理程序中所有的應用程式網域會共用無定義域組件的程式碼。如果許多 Web 應用程式都使用同一個強式名稱組件,而且每個應用程式均授予了不同的使用權限,或者如果應用程式網域每次重新啟動的權限授權不同,就會產生問題。在這種情況下,可能會出現下列的錯誤訊息:「組件 <assembly>.dll 的安全權限授權集在 appdomains 間不相容」。
為了避免這種錯誤,必須將強式名稱組件放在全域組件快取中,而非放在應用程式的專用 \bin 目錄中。
Authenticode 與強式名稱提供以數位方式為組件簽名的兩種不同方式。Authenticode 可以讓您使用 X.509 憑證為組件簽名。這是使用 Signcode.exe 公用程式執行,程式會將完整的 X.509 憑證公開金鑰部分加入組件中。這利用憑證鏈結和憑證授權單位確保信任。使用 Authenticode (不同於強式名稱),發行者信任的實作很複雜,在驗證發行者識別身份時牽涉到網路通訊。
Authenticode 簽章與強式名稱是針對解決不同問題而開發,兩者不應該搞混。具體來說:
| • | 強式名稱可以有效識別組件。 |
| • | Authenticode 簽章可以有效識別程式碼發行者。
|
您可以同時使用強式名稱和 Authenticode 簽章設定程式碼存取安全性 (CAS) 原則,將權限授予特定的組件。但是,只有 Internet Explorer 主機會建立從 Authenticode 簽章取得的 Publisher 舉證物件,而非由 ASP.NET 主機建立。因此在伺服器端,不能使用 Authenticode 簽章識別特定的組件 (透過程式碼群組),請改用強式名稱。
如需 CAS、CAS 原則及程式碼群組的詳細資訊,請參閱單元 8<程式碼存取安全性實務>。
[表 7.1] 比較強式名稱與 Authenticode 簽章的功能。
[表 7.1] 強式名稱與 Authenticode 簽章的比較
| 功能 | 強式名稱 | Authenticode |
有效識別組件 | 是 | 否 |
有效識別發行者 | 非必要。視組件開發人員,使用公開金鑰代表發行者 | 是 |
發行者的公開金鑰可以撤銷。 | 否 | 是 |
版本 | 是 | 否 |
命名空間與類型名稱的唯一性 | 是 | 否 |
完整性 (確定組件並未遭到竄改) | 是 | 是 |
舉證做為 CAS 原則的輸入 | 是 | IE 主機 是。 |
依照是否受信任,決定是否需要使用者輸入 | 否 | 是 (快顯對話方塊) |
在組件中可以使用兩種授權,以控制對類別和類別成員的存取:
| • | 角色為基礎的授權,根據使用者識別身份和角色成員授權存取。如果組件是 ASP.NET Web 應用程式或 Web 服務的一部分,則在組件中使用角色為基礎的授權時,所授權的是由連接至目前 Web 要求之 IPrincipal 物件代表的識別身份,這個物件可以透過 Thread.CurrentPrincipal 和 HttpContext.Current.User 存取。這個識別身份是經過驗證的一般使用者識別身份,或匿名的網際網路使用者識別身份。如需在 Web 應用程式中使用主體為基礎之授權的詳細資訊,請參閱單元 10<建置安全的 ASP.NET 網頁和控制項>中的<授權>。 |
| • | 根據舉證,例如組件的強式名稱或位置,呼叫程式碼的程式碼存取安全性。如需詳細資訊,請參閱單元 8<程式碼存取安全性實務>中的<授權>。 |
不要在傳回用戶端的例外狀況訊息中,顯示應用程式的執行詳細資料。這項資訊可以幫助惡意使用者計劃對您的應用程式的攻擊。若要提供正確的例外狀況管理:
| • | 使用結構化例外處理 (Structured Exception Handling)。 |
| • | 不要記錄機密資料。 |
| • | 不要顯露系統或機密的應用程式資訊。 |
| • | 考慮例外狀況篩選器的問題。 |
| • | 考慮例外狀況管理架構。 |
Microsoft Visual C# 與 Microsoft Visual Basic .NET 提供結構化例外處理。C# 提供 try / catch 和 finally 建構。將程式碼放在try 區塊中加以保護,執行 catch 區塊,以記錄並處理例外狀況。另外使用 finally 建構,確保重要的系統資源 (例如連線) 會加以關閉,不受是否發生例外狀況的影響。
try
{
// Code that could throw an exception
}
catch (SomeExceptionType ex)
{
// Code to handle the exception and log details to aid
// problem diagnosis
}
finally
{
// This code is always run, regardless of whether or not
// an exception occurred. Place clean up code in finally
// blocks to ensure that resources are closed and/or released.
}
使用結構化例外處理代替從方法傳回錯誤碼,因為常會忘了檢查傳回碼,產生不安全的模式。
Exception 物件中包含了豐富的例外狀況詳細資料,對開發人員和攻擊者都同樣有用。將詳細資料寫入事件日誌中,記錄在伺服器上以協助診斷問題。避免記錄機密或私人資料,例如使用者密碼。此外請確定,例外詳細資料不允許傳播到應用程式範圍之外,傳播到用戶端,如下一主題所述。
不要向呼叫者顯露太多的資訊。例外詳細資料可能包含作業系統與 .NET Framework 版本編號、方法名稱、電腦名稱、SQL 命令陳述式、連接字串,以及對攻擊者很有用的其他詳細資料。將詳細的錯誤訊息記錄在伺服器上,只將一般錯誤訊息傳回給一般使用者。
在 ASP.NET Web 應用程式或 Web 服務的環境中,您可以適當地設定 <customErrors> 項目來做到這點。如需詳細資訊,請參閱單元 10<建置安全的 ASP.NET 網頁和控制項>。
如果程式碼使用例外狀況篩選器,很可能出現安全性問題的弱點,因為篩選器中的程式碼在呼叫堆疊之上,可以在 finally 區域的程式碼之前執行。請確定您並不依賴 finally 區塊中的狀態變更,因為狀態會在例外篩選器執行之前變更。例如,請考慮以下的程式碼:
// Place this code into a C# class library project
public class SomeClass
{
public void SomeMethod()
{
try
{
// (1) Generate an exception
Console.WriteLine("1> About to encounter an exception condition");
// Simulate an exception
throw new Exception("Some Exception");
}
// (3) The finally block
finally
{
Console.WriteLine("3> Finally");
}
}
}
// Place this code into a Visual Basic.NET console application project and
// reference the above class library code
Sub Main()
Dim c As New SomeClass
Try
c.SomeMethod()
Catch ex As Exception When Filter()
' (4) The exception is handled
Console.WriteLine("4> Main: Catch ex as Exception")
End Try
End Sub
' (2) The exception filter
Public Function Filter() As Boolean
' Malicious code could do something here if you are relying on a state
' change in the Finally block in SomeClass in order to provide security
Console.WriteLine("2> Filter")
Return True ' Indicate that the exception is handled
End Function
上面的範例中,使用 Visual Basic .NET 呼叫 C# 類別庫程式碼,因為 Visual Basic .NET 支援例外狀況篩選器,這和 C# 不同。
如果您建立兩個專案,然後再執行程式碼,產生的輸出如下所示:
1> About to encounter an exception condition 2> Filter 3> Finally 4> Main: Catch ex as Exception
您可以從這個輸出中看到,例外狀況篩況篩選器是在 finally 區塊中的程式碼之前執行。如果您的程式碼設定會影響 finally 區塊安全性決定的狀態,則呼叫您程式碼的惡意程式碼就可以加入一個例外狀況篩選器,來利用這個弱點。
正式的例外狀況管理系統可以幫助您增強系統的可支援性和可維護性,確保您以一致的方式偵測、記錄及處理例外狀況。
如需有關如何建立例外狀況管理架構和 .NET 應用程式最佳例外狀況管理實務的詳細資訊,請參閱 MSDN Library (http://msdn.microsoft.com/library/en-us/dnbda/html/exceptdotnet.asp) 中的《例外狀況管理架構指南》(英文)。
標準化問題是存取檔案系統之程式碼的主要考量。如果您可以選擇,請不要根據輸入檔案名稱來決定安全性,因為一個檔案名稱可以用許多方式來表示。如果您的程式碼需要以使用者提供的檔案名稱存取檔案,請採取步驟,確定惡意使用者無法使用您的組件來取得機密資料的存取權或覆蓋資料。
下列建議會協助您增強檔案 I/O 的安全性:
| • | 避免不受信任的檔案名稱輸入。 |
| • | 不要信任環境變數。 |
| • | 驗證輸入檔案名稱。 |
| • | 將檔案 I/O 限制在應用程式的範圍內。 |
避免撰寫會從呼叫者接受檔案或路徑輸入的程式碼,閱讀和寫入資料時改用固定的檔案名稱和位置。這可以確保您的程式碼不會被強迫存取任何的檔案。
儘可能使用絕對檔案路徑。不要信任環境變數,以建構檔案路徑,因為您無法保證環境變數的值。
如果不需要從呼叫者接收輸入檔案名稱,請確定檔案名稱的嚴謹格式,以便判斷是否有效。驗證輸入檔案路徑具體上分成兩部分。您必須:
| • | 檢查有效的檔案系統名稱。 |
| • | 檢查應用程式範圍定義的有效位置。例如,是否位於您應用程式的目錄階層中? |
若要驗證路徑與檔案名稱,請使用 System.IO.Path.GetFullPath 方法,如以下程式碼範例中所示。這個方法也會將提供的檔案名稱正式化。
using System.IO;
public static string ReadFile(string filename)
{
// Obtain a canonicalized and valid filename
string name = Path.GetFullPath(filename);
// Now open the file
}
正式化程序中,GetFullPath 會執行下列檢查:
| • | 確定檔案名稱未包含 Path.InvalidPathChars 所定義的任何無效的字元。 |
| • | 確定檔案名稱代表檔案,而非其他裝置類型,例如實體磁碟機、名稱管道、郵件插槽或 DOS 裝置,例如 LPT1、COM1、AUX 和其他裝置。 |
| • | 確定結合後的路徑與檔案名稱不會太長。 |
| • | 移除多餘的字元,例如字尾的點。 |
| • | 拒絕使用 //?/ 格式的檔案名稱。 |
確定檔案系統檔案名稱正確後,通常必須檢查檔案在您的應用程式範圍內是否有效。例如,您必須確定它是否在應用程式的目錄階層內,並且確定您的程式碼無法存取檔案系統中的任何檔案。如需有關如何使用程式碼存取安全性,以限制檔案 I/O 的詳細資訊,請參閱單元 8<程式碼存取安全性實務>中的<檔案 I/O>。
撰寫事件日誌程式碼時,請考慮竄改和資訊洩露的潛在威脅。例如,攻擊者能夠存取事件日誌,擷取機密的資料嗎?攻擊者能夠刪除日誌或清除特定記錄,掩飾其蹤跡嗎?
Windows 安全性會限制使用事件檢視器等系統管理工具直接存取事件日誌。您的主要考量,應該是確定惡意使用者無法使用您所撰寫的事件日誌程式碼,未經授權地存取事件日誌。
若要防止洩露機密資料,首先是不要記錄這些資料。例如,不要記錄帳戶認證。此外,如果您的程式碼執行的所有操作是使用 EventLog.WriteEvent 寫入新記錄,也無法利用它來寫入現有的記錄或刪除事件日誌。這種情況下要處理的主要潛在威脅,是如何防止惡意呼叫者連續不斷呼叫您的程式碼,強制記錄檔循環覆蓋先前的記錄項目,以掩飾其蹤跡。處理這種問題的最佳方式是使用 Out-of-Band 機制,在事件日誌接近其極限值時,立即使用 Windows Instrumentation 警示操作。
最後,可以使用程式碼存取安全性和 EventLogPermission ,在您的程式碼存取事件日誌時,為可以執行的操作加上特定的限制。例如,如果您撰寫只需要從事件日誌讀取記錄的程式碼,則可以使用只支援瀏覽存取的 EventLogPermissin 限制。如需有關如何限制事件日誌記錄程式碼的詳細資訊,請參閱單元 8<程式碼存取安全性實務>中的<事件日誌>。
登錄可以提供存放機密應用程式設定資料的安全位置,例如加密的資料庫連線字串等資料。您可以將設定資料存放在單一的本機機碼 (HKEY_LOCAL_MACHINE) 或目前使用者機碼 (HKEY_CURRENT_USER) 底下。不論使用哪一種方式,請確定您使用 DPAPI 將資料加密並儲存加密的資料,而非純文字。
如果將設定資料儲存在 HKEY_LOCAL_MACHINE 底下,請記住本機電腦上的任何處理程序都可能存取資料。若要限制存取,請將限制存取控制清單 (ACL) 套用到特定的登錄機碼,以限制存取系統管理員和特定的處理程序或執行緒語彙基元。如果使用 HKEY_LOCAL_MACHINE,則在安裝時儲存設定資料和以後維護都比較容易。
如果您的安全性要求需要更安全的存放解決方案,請使用 HKEY_CURRENT_USER 底下的機碼。這種方法表示您不必明確地設定 ACL,因為會根據處理序識別身份,自動限制對目前使用者機碼的存取。
HKEY_CURRENT_USER 允許限制更嚴格的存取,因為若是載入與目前執行緒或處理序權杖關聯的使用者設定檔,處理序只能存取目前使用者機碼。
.NET Framework 1.1 版會在 Windows 2000 上載入 ASPNET 帳戶的使用者設定檔。而在 Windows Server 2003 上,只有使用 ASP.NET 處理序模型時,才會載入這個帳戶的設定檔。如果在 Windows Server 2003 上使用 Information Services (IIS) 6 處理序模型,IIS 6 不會明確載入設定檔。
注意 .NET Framework 1.0 版不會載入 ASPNET 使用者設定檔,這降低了 HKEY_CURRENT_USER 的實用性。
以下程式碼片段顯示如何使用 Microsoft.Win32.Registry 類別,從 HKEY_CURRENT_USER 機碼底下讀取加密的資料庫連線字串。
using Microsoft.Win32;
public static string GetEncryptedConnectionString()
{
return (string)Registry.
CurrentUser.
OpenSubKey(@"SOFTWARE\YourApp").
GetValue("connectionString");
}
如需有關如何使用程式碼存取安全性 RegistryPermission 限制登錄存取程式碼的詳細資訊,例如限制在特定的機碼,請參閱單元 8<程式碼存取安全性實務>中的<登錄>。
程式碼存取資料庫時要考量的兩大因素,是如何安全地管理資料庫連線字串,以及如何建構 SQL 陳述式和驗證輸入,以防止 SQL 注入攻擊。此外,在撰寫資料存取程式碼時,請考慮所選 ADO.NET 資料提供者的權限要求。如需這些和其他資料存取問題的詳細資訊,請參閱單元 14<建置安全的資料存取>。
如需有關如何使用 SqlClientPermission 限制對使用 ADO.NET SQL Server 資料提供者之 SQL Server 的資料存取,請參閱單元 8<程式碼存取安全性實務>中的<資料存取>。
Managed 程式碼、.NET Framework 和公用語言執行時間,可以消除通常出現在 Unmanaged 程式碼中許多重要安全性相關的弱點。程式碼的類型安全是 .NET Framework 提供幫助的一個很好的例子。這讓 Managed 程式碼內幾乎不可能出現緩衝區溢位,幾乎可以消除堆疊為主的程式碼注入的潛在威脅。
但是,如果現有的 COM 元件或 Win32 DLL 必須重新使用,則請使用 Platform Invocation Services (P/Invoke) 或 COM Interop 層,以繫結至您的組件。
呼叫 Unmanaged 程式碼時,Managed 程式碼會驗證傳遞到 Unmanaged API 的每個輸入參數,以避免發生潛在的緩衝區溢位。此外,處理從 Unmanaged API 傳回的輸出參數時,請務必小心。
您可以將對 Unmanaged 程式碼的呼叫隔離在另一個包裝函式組件中。這可以讓您沙箱隔離高特殊權限程式碼,並將程式碼存取安全性權限要求隔離在特定的組件。如需沙箱隔離和呼叫 Unmanaged 程式碼時應套用之其他程式碼存取安全性相關指導方針的詳細資訊,請參閱單元 8<程式碼存取安全性實務>中的<Unmanaged 程式碼>。下列建議可以在不使用明確程式碼存取安全性程式設計技術的情形下,增強 Unmanaged API 呼叫的安全性:
| • | 驗證輸入與輸出字串參數。 |
| • | 驗證陣列界限。 |
| • | 檢查檔案路徑長度。 |
| • | 以 /GS 參數編譯 Unmanaged 程式碼。 |
| • | 檢查 Unmanaged 程式碼中是否有「危險的」API。 |
傳遞至 Unmanaged API 的字串參數是緩衝區溢位的主要來源。檢查包裝函式程式碼中任何輸入字串的長度,確定並未超出 Unmanaged API 定義的限制。如果 Unmanaged API 接受字元指標,則除非您能夠存取 Unmanaged 來源,否則將不會知道允許的最大字串長度。例如,以下是一個常見的弱點。
void SomeFunction( char *pszInput )
{
char szBuffer[10];
// Look out, no length checks. Input is copied straight into the buffer
// Check length or use strncpy
strcpy(szBuffer, pszInput);
. . .
}
如果因為您未擁有 Unmanaged 程式碼而無法檢查程式碼,請務必謹慎地傳入長的輸入字串,嚴格地測試 API。
如果您的程式碼使用 StringBuilder 接收從 Unmanaged API 傳來的字串,請確定它能夠保存 Unmanaged API 可以傳回的最長的字串。
如果您使用陣列將輸入傳遞到 Unmanaged API,請確定 Managed 包裝函式會確認未超出陣列的容量。
如果 Unmanaged API 接受檔案名稱和路徑,請確認長度並未超過 260 個字元。此限制是由 Win32 MAX_PATH 常數定義。Unmanaged 程式碼通常會使用這個長度的緩衝區來操作檔案路徑。
注意 目錄名稱與登錄機碼的最大長度只能有 248 個字元。
如果您擁有 Unmanaged 程式碼,請使用 /GS 參數來編譯以啟用堆疊探查 (Stack Probe),協助偵測緩衝區溢位。如需 /GS 參數的詳細資訊,請參閱「Microsoft 知識庫」文件 325483 《WebCast: 編譯器安全性檢查:/GS 編譯器參數》(英文),網址:http://support.microsoft.com/default.aspx?scid=kb;EN-US;q325483。
如果您能夠存取所呼叫之 Unmanaged 程式碼的原始碼,應該徹底進行程式碼的檢閱,特別要注意參數處理,確定不可能發生緩衝區溢位,而且未使用有潛在危險性的 API。如需詳細資訊,請參閱單元 21<程式碼的檢閱>。
委派是類型安全函式指標的 Managed 同等項目,.NET Framework 使用它來支援事件。委派物件維護對方法的參照,呼叫委派時就會呼叫這個方法。事件允許多個方法登錄成事件處理常式。事件發生時,會呼叫所有的事件處理常式。
如果您的組件顯露委派或事件,必須知道任何程式碼都可以將方法與委派關聯,而您無法預知程式碼的動作。最安全的原則是不接受來自不信任呼叫者的委派。如果您的組件使用強式名稱,而且未包含 AllowPartiallyTrustedCallersAttribute,則只有完全信任呼叫者可以傳遞委派給您。
如果您的組件支援部分信任呼叫者,請考慮惡意程式碼可能傳遞委派給您的其他潛在威脅。如需應付這種威脅的降低風險技術,請參閱單元 8<程式碼存取安全性實務>。
如果您必須跨過 .NET 遠端界限 (跨越應用程式網域、處理序或電腦),依照值整理類別,或想要保留物件狀態以建立平面資料串流,可能是檔案系統上的存放區,則必須在類別加入序列化支援。
在預設的情況下,類別無法序列化。類別若是以 SerializableAttribute 標記,或是從 ISerializable 衍生,就能夠序列化。如果使用序列化:
| • | 請勿將機密資料序列化。 |
| • | 驗證序列化的資料串流。 |
如果您的類別包含機密資料,最好不要支援序列化。如果必須將類別序列化,而類別又包含機密資料,請避免將包含機密資料的欄位序列化。方式是,實作 ISerializable 來控制序列化行為,或使用 NonSerialized 屬性修飾包含機密資料的欄位。在預設的狀況下,所有私密和公開欄位都會序列化。
以下範例顯示如何使用 NonSerialized 屬性,確保包含機密資料的特定欄位無法序列化。
[Serializable]
public class Employee {
// OK for name to be serialized
private string name;
// Prevent salary being serialized
[NonSerialized] private double annualSalary;
. . .
}
或者實作 ISerializable 介面,明確地控制序列化程序。如果必須將機密的資料項目序列化,請考慮先將資料加密。解除物件序列化的程式碼必須存取解密金鑰。
您從序列化資料串流建立物件執行個體時,請不要假設串流包含的是正確的資料。若要避免將潛在的損壞性資料注入物件,請在重新建立時驗證每個檔案,如以下程式碼範例所示。
public void DeserializationMethod(SerializationInfo info, StreamingContext cntx)
{
string someData = info.GetString("someName");
// Use input validation techniques to validate this data.
}
如需輸入驗證技術的詳細資訊,請參閱單元 10<建置安全的 ASP.NET 網頁和控制項>中的<輸入驗證>。
如果您的程式碼支援部分信任呼叫者,則必須解決其他潛在的威脅。例如,惡意程式碼可能傳遞序列化資料串流,或者可能嘗試將物件的資料序列化。如需有關解決這類威脅的降低風險技術,請參閱單元 8<程式碼存取安全性實務>。
多執行緒程式碼中的競爭情形造成的錯誤可能產生安全性弱點,以及受時間相關錯誤影響的不穩定的程式碼。開發多執行緒組件時,請考慮下列建議:
| • | 不要將安全性檢查的結果快取。 |
| • | 考慮使用模擬權杖。 |
| • | 同步處理靜態類別建構函式。 |
| • | 同步處理處理方法。 |
如果您的多執行緒程式碼會快取安全性檢查的結果 (可能是快取在靜態變數中),程式碼很可能出現弱點,如以下程式碼範例所示。
public void AccessSecureResource()
{
_callerOK = PerformSecurityDemand();
OpenAndWorkWithResource();
_callerOK = false;
}
private void OpenAndWorkWithResource()
{
if (_callerOK)
PerformTrustedOperation();
else
{
PerformSecurityDemand();
PerformTrustedOperation();
}
}
如果有通往 OpenAndWorkWithResource 的其他路徑,而且有另一個執行緒呼叫同一物件上的方法,第二個執行緒很可能因為看到前一個執行緒設定的 _callerOK=true,因此省略安全性要求。
建立新執行緒時,新執行緒會採用處理序層級權杖所定義的安全性內容。如果父執行緒在建立新執行緒時模擬,模擬權杖不會傳遞到新執行緒。
如果使用靜態類別建構函式,請確定這些建構函式不會因為競爭情況而出現弱點。例如,如果這些建構函式操作靜態狀態,請加入執行緒同步處理,以避免可能的弱點。
如果開發非同步處理的 Dispose 執行,不同的執行緒可能會呼叫 Dispose 程式碼許多次。以下程式碼範例顯示這種情況的例子。
void Dispose()
{
if (null != _theObject)
{
ReleaseResources(_theObject);
_theObject = null;
}
}
在此範例中,兩個執行緒可能在第一個執行將 _theObject 參照設定為 Null 之前執行程式碼。根據 ReleaseResources 方法提供的功能,很可能出現安全性弱點。
使用反射,可以動態地載入組件、搜索類型的資訊及執行程式碼,也可以取得物件的參照,並取得或設定 Private 成員。這包含許多安全性含意:
| • | 如果您的程式碼使用反射來反射其他類型,請確定只有信任的程式碼能夠呼叫您。使用程式碼存取安全性權限的要求,來授權呼叫程式碼。如需詳細資訊,請參閱單元 8<程式碼存取安全性實務>。 |
| • | 如果您動態載入組件,例如使用 System.Reflection.Assembly.Load,請不要使用從不受信任來源傳遞給您的組件或類型名稱。 |
| • | 如果您的組件會動態產生程式碼,為呼叫者執行操作,請確定呼叫者絕對無法影響產生的程式碼。如果呼叫者是在比產生程式碼之組件更低的信任等級操作,這種問題更值得注意。 |
| • | 如果您的程式碼產生是依賴呼叫者的輸入,要特別注意安全性弱點。驗證產生之程式碼中作為字串文字的任何輸入字串和逸出引號字元,確定呼叫者無法破壞文字,注入程式碼。通常,如果呼叫者有辨法影響程式碼產生,使其無法編譯,就可能發生安全性弱點。 |
如需詳細資訊,請參閱 MSDN Library 中的《Secure Coding Guidelines for the .NET Framework》。
如果您關心保護智慧財產,可以使用模糊化工具,使得在您的組件之 MSIL 程式碼中使用解譯器變得非常難。模糊化工具會混淆人員對 MSIL 指令的解釋,防止解譯成功。
模糊化並非絕對安全的,建立的安全性解決方案不可以完全依賴模糊化。但是,模糊化並不能應付因為將程式碼反向工程產生的威脅。模糊化工具通常提供下列好處:
| • | 幫助保護您的智慧財產。 |
| • | 隱匿路徑,讓攻擊者更難破解安全性邏輯。 |
| • | 將內部成員變數的名稱弄亂。瞭解程式碼更困難。 |
| • | 將字串加密。攻擊者通常會嘗試搜尋特定的字串,以找出關鍵的機密邏輯。字串加密讓攻擊者更難搜尋。 |
.NET Framework 具有許多協力廠商的模糊化工具。Microsoft Visual Studio® .NET 2003 開發系統就包含一套工具,PreEmptive Solutions 的 Dotfuscator Community Edition。您也可以從 http://www.preemptive.com/dotfuscator 取得。如需詳細資訊,請參閱http://www.gotdotnet.com/team/csharp/tools/default.aspx 所列的模糊化工具清單。
密碼編譯是用於保護資料的最重要工具之一。密碼編譯可以用於提供資料穩私性和雜湊演算法,產生固定和壓縮的資料代表,防止資料遭到竄改。此外,也可以使用數位簽章進行驗證。
保護傳輸或儲存之資料的安全時,您可以使用加密。有些加密演算法的執行能力較佳,有些則加密較強固。通常,加密金鑰的大小較大時,安全性會提高。
使用密碼編譯時最常犯的兩種錯誤,是開發自己的加密演算法和未將加密金鑰保全。加密金鑰必須小心處理。得到您的加密金鑰的攻擊者可以存取您的加密資料。
考慮的主要問題如下:
| • | 使用平台提供的密碼編譯服務 |
| • | 金鑰產生 |
| • | 金鑰存放 |
| • | 金鑰交換 |
| • | 金鑰維護 |
不要建立您自己的密碼編譯執行。這樣的執行極不可能像平台 (作業系統和 .NET Framework) 提供的產業標準演算法那麼安全。Managed 程式碼應使用 System.Security.Cryptography 命名空間提供的演算法加密、解密、雜湊、隨機數字產生及數位簽章。
這個命名空間中的許多類型會包裝作業系統 CryptoAPI,而其他則會在 Managed 程式碼中執行演算法。
下列建議適用於建立加密金鑰:
| • | 產生隨機金鑰。 |
| • | 使用 PasswordDeriveBytes 進行密碼為基礎的加密。 |
| • | 偏好大型金鑰。 |
如果必須使用程式產生加密金鑰,請使用 RNGCryptoServiceProvider 建立金鑰和初始化向量,不要使用 Random 類別。不同於 Random 類別的是,RNGCryptoServiceProvider 會利用密碼編譯建立 FIPS-140 相容的增強式隨機數字。以下程式碼顯示如何使用此函式。
using System.Security.Cryptography; . . . RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); byte[] key = new byte[keySize]; rng.GetBytes(key);
System.Security.Cryptography.DeriveBytes 命名空間提供 PasswordDeriveBytes,可以在根據使用提供的密碼將資料加密時使用。解密時,使用必須提供和加密時使用的相同密碼。
請注意,這種方法並非用於密碼驗證。將密碼檢查器儲存成含 Salt 數值順序的雜湊數值格式,以驗證使用者的密碼。使用 PasswordDeriveBytes 產生密碼為基礎之加密的金鑰。
PasswordDeriveBytes 接受密碼、Salt、加密演算法、雜湊演算法、金鑰大小 (以位元為單位) 及初始化向量資料,以建立加密所使用的對稱金鑰。
使用金鑰將資料加密後,請從記憶體清除,但是保留 Salt 和初始化向量。這些數值應該加以保護,解密時也需要這些數值來重新產生金鑰。
如需儲存以 Salt 雜湊之密碼的詳細資訊,請參閱單元 14<建置安全的資料存取>。
產生加密金鑰或金鑰組時,請使用演算法允許的最大金鑰大小。這不一定會讓演算法更安全,但是可以大幅增加成功地暴力式攻擊金鑰所需的時間。以下程式碼顯示如何尋找特定演算法所支援的最大金鑰大小。
private int GetLargestSymKeySize(SymmetricAlgorithm symAlg)
{
KeySizes[] sizes = symAlg.LegalKeySizes;
return sizes[sizes.Length].MaxSize;
}
private int GetLargestAsymKeySize(AsymmetricAlgorithm asymAlg)
{
KeySizes[] sizes = asymAlg.LegalKeySizes;
return sizes[sizes.Length].MaxSize;
}
如果可以,您可以使用平台提供的加密解決方案,讓您可以省掉應用程式中的金鑰管理。但是,您有時候也需要使用要求您存放金鑰的加密解決方案。使用安全的位置來存放金鑰是很重要的。請使用下列技術,以協助您防止發生金鑰存放的弱點:
| • | 使用 DPAPI 以避免金鑰管理。 |
| • | 不要將金鑰存放在程式碼中。 |
| • | 限制對持續之金鑰的存取。 |
DPAPI 是 Microsoft Windows 2000 提供的原始加密/解密功能。使用 DPAPI 的主要優點之一,在於加密金鑰是由作業系統管理,因為金鑰是由與呼叫 DPAPI 函式的處理序帳戶 (如果執行緒是模擬的,則是執行緒帳戶) 關聯的密碼衍生。
使用者金鑰相對於電腦金鑰
您可以利用使用者金鑰或電腦金鑰,以 DPAPI 執行加密。在預設的狀況下,DPAPI 會使用使用者金鑰。這表示,只有在將資料加密之使用者帳戶的安全性範圍下執行的執行緒,能夠將資料解密。您可以指示 DPAPI 傳遞 CRYPTPROTECT_LOCAL_MACHINE 旗標至 CryptProtectData API,來使用電腦金鑰。這種情況下,目前電腦上的任何使用者都可以將資料解密。
唯有用於執行加密的帳戶載入了使用者設定檔,才能使用使用者金鑰選項。如果您在未載入使用者設定檔的環境執行程式碼,則不能輕易使用使用者存放區,應改成選擇電腦存放區。
.NET Framework 1.1 版會載入使用者設定檔,讓使用的 ASPNET 帳戶在 Windows 2000 上執行 Web 應用程式。.NET Framework 1.0 版不會載入此帳戶的設定檔,因此運用使用者金鑰來使用 DPAPI 會更為困難。
如果您使用電腦金鑰選項,則可以使用 ACL 保護加密資料的安全,例如登錄機碼中的資料,並使用這個方法限制哪些使用者能夠存取加密的資料。為了增加安全性,您也可以傳遞選擇性的熵值到 DPAPI 函式。
注意 熵值是可以傳遞到 DPAPI CryptProtectData 與 CryptUnprotectData 函式的任何額外隨機值。必須使用和加密資料時使用的相同值來將資料解密。電腦金鑰選項表示電腦上的任何使用者都能將資料解密。加入熵後,使用者也必須知道熵值。
使用熵的缺點是,您必須像管理金鑰一樣管理熵值。若要避免熵的管理問題,請使用沒有熵的的電腦存放區,並在呼叫 DPAPI 程式碼之前驗證使用者與程式碼 (使用程式碼存取安全性)。
如需從 ASP.NET Web 應用程式使用 DPAPI 的詳細資訊,請參閱《建置安全的 ASP.NET 應用程式》中<How To>一節的<How To: 建立 DPAPI 程式庫>,網址 http://msdn.microsoft.com/library/en-us/dnnetsec/html/SecNetHT07.asp。
不要將金鑰存放在程式碼中,因為固定寫在編譯之組件中的金鑰可以使用 ILDASM 等工具反組譯。
將金鑰存放在持續存放區中以供執行時間使用時,請使用適當的 ACL 及限制對金鑰的存取。只有在執行時間時,才授予系統管理員、SYSTEM 和程式碼識別身份的存取權,例如 ASPNET 或網路服務帳戶。
備份金鑰時,請勿存放在純文字檔中,請使用 DPAPI 或增強式密碼加密,然後放在卸除式媒體上。
有些應用程式需要透過不安全的網路進行加密金鑰的安全交換。您可能需要口頭上傳達金鑰,或透過安全電子郵件傳送。更安全地交換對稱金鑰的方法,是使用公開金鑰加密。使用這種方法時,您使用可以驗證之憑證中其他人的公開金鑰,將要交換的對稱金鑰加密。在下列情況下,憑證就視為有效:
| • | 在憑證指定的日期範圍內使用。 |
| • | 憑證鏈結中所有的簽章都能夠驗證。 |
| • | 類型正確。例如,電子郵件憑證不能做為 Web 伺服器憑證。 |
| • | 可以向上驗證到信任的根授權單位。 |
| • | 不在發行者的憑證廢止清單 (CRL) 中。 |
安全性依賴長時間保持金鑰安全。請套用下列金鑰維護的建議:
| • | 定期變換金鑰。 |
| • | 保護匯出的私密金鑰。 |
您可以不時變更加密金鑰,因為靜態的機密長時間下來很容易被發現。您是否把它抄在某個地方?知道機密的系統管理員 Bob 是否調動職位,或已經離職?您長時間使用相同的工作階段金鑰為通訊加密嗎?請不要過度使用金鑰。
金鑰洩露
許多情況下,金鑰都可能會洩露。例如,您可能會遺失金鑰,或發現攻擊者已竊取或找到金鑰。
如果用於非對稱加密和金鑰交換的私密金鑰洩露了,請不要繼續使用,並通知公開金鑰的使用者該金鑰已經洩露。如果您使用金鑰簽署文件,則必須重新簽署。
如果憑證的私密金鑰洩露,請聯絡發行的憑證授權單位,將憑證加入憑證廢止清單中。另外請變更儲存金鑰的方式,以避免以後再發生洩露。
匯出 Rivest、Shamir 及 Adleman (RSA) 或 Digital Signature Algorithm (DSA) 私密金鑰時,請使用 PasswordDeriveBytes。RSA 與 DSA 類別包含一個 ToXmlString 方法,可以讓您從金鑰容器匯出公開或私密金鑰,或兩者都匯出。這個方法會以純文字匯出私密金鑰。如果匯出的私密金鑰是要安裝在 Web Farm 中的多部伺服器上,建議您在匯出私密金鑰後,使用 PasswordDeriveBytes 產生對稱金鑰,將金鑰加密,如以下程式碼範例所示。
PasswordDeriveBytes deriver = new PasswordDeriveBytes(<strong password>, null);
byte[] ivZeros = new byte[8];//This is not actually used but is currently required.
//Derive key from the password
byte[] pbeKey = deriver.CryptDeriveKey("TripleDES", "SHA1", 192, ivZeros);
這個單元為您示範了如何應用各種技術來增強 Managed 程式碼的安全性。單元裡的技術可以套用至所有類型的 Managed 組件,包括網頁、控制項、公用程式程式庫等。如需適用於特定類型組件的特定建議,請參閱本指南第三部中的其他建立單元。
若要進一步增強組件的安全性,可以使用明確程式碼存取安全性編碼技術,如果您的組件支援部分信任呼叫者,這一點特別重要。如需使用程式碼存取安全性的詳細資訊,請參閱單元 8<程式碼存取安全性實務>。
如需其他相關文章,請參考下列資源:
| • | 如需從 ASP.NET Web 應用程式使用 DPAPI 的詳細資訊,請參閱《Microsoft patterns & practices 第 1 卷,建置安全的 ASP.NET Web 應用程式:驗證、授權和安全的通訊》中<How To>一節的<How To: 建立 DPAPI 程式庫>(英文),網址 http://msdn.microsoft.com/library/en-us/dnnetsec/html/SecNetHT07.asp。 |
| • | 如需 .NET Framework 安全編碼指導方針的詳細資訊,請參閱 MSDN 文章《.NET Framework 安全編碼指導方針》(英文),網址 http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/seccodeguide.asp。 |
| • | Michael Howard 在他的 MSDN 專欄《程式碼安全性》(英文),介紹撰寫安全程式碼的技術,並示範如何將程式碼加入您自己的應用程式中,網址 http://msdn.microsoft.com/security/securecode/columns/default.aspx。 |