程式碼存取安全性和 ADO.NET

.NET Framework 會提供以角色為基礎的安全性和程式碼存取安全性 (CAS),而這兩種安全性都是使用通用語言執行平台 (CLR) 所提供的通用基礎結構所實作的。 在 Unmanaged 程式碼的作用範圍內,大多數應用程式都是以使用者或主體的權限執行。 因此,當擁有更高權限的使用者執行惡意或充滿錯誤的軟體時,就可能損害電腦系統和竊取私人資料。

相較之下,在 .NET Framework 中執行的 Managed 程式碼具有單獨套用至程式碼的程式碼存取安全性。 系統是否允許執行程式碼會取決於程式碼的來源或程式碼識別的其他層面,而非單獨取決於主體的識別。 這能降低 Managed 程式碼被誤用的可能性。

注意

在所有版本的 .NET Framework 與 .NET 中,程式碼存取安全性 (CAS) 均已被取代。 當使用 CAS 相關 API 時,最新版本的 .NET 不會接受 CAS 註釋,並會產生錯誤。 開發人員應尋求替代方案來完成安全性工作。

程式碼存取權限

執行程式碼時,它會顯示 CLR 安全性系統所評估的辨識項。 一般來說,這個辨識項會洩露程式碼的來源,包含 URL、大小和區域,以及用來確保組件識別的數位簽章。

CLR 僅允許程式碼執行該程式碼有權執行的這些作業。 程式碼可以要求權限,而且系統會根據系統管理員所設定的安全性原則來接受這些要求。

注意

CLR 中執行的程式碼不能授與其本身的使用權限。 例如,程式碼可以要求而且被授與少於安全性原則允許的權限,但是它絕不會被授與更多權限。 授與權限時,系統是以完全沒有權限開始,然後加入執行特定工作的最少權限。 如果一開始便使用所有權限,然後再拒絕個別的權限,則會造成應用程式不安全,因為可能會授與超出必要的權限而導致意外安全性漏洞。 如需詳細資訊,請參閱 設定安全性原則安全策略管理

程式碼存取權限有三種類型:

  • Code access permissions衍生自 CodeAccessPermission 類別。 為了存取受保護資源 (例如檔案和環境變數) 以及執行受保護作業 (例如存取 Unmanaged 程式碼),因此需要權限。

  • Identity permissions 代表可識別組件的特性。 系統會根據辨識項 (可能包含數位簽章或程式碼來源等項目),將權限授與組件。 識別權限也衍生自 CodeAccessPermission 基底類別 (Base Class)。

  • Role-based security permissions是以主體是否具有指定的識別或屬於指定角色成員為基礎。 PrincipalPermission 類別允許針對使用中主體進行宣告式和必要的權限檢查。

為了判斷程式碼是否經授權可存取資源或執行某項作業,執行階段的安全性系統會周遊呼叫堆疊,並比較每個呼叫端被授與的權限與要求的權限。 如果呼叫堆疊中的任何呼叫端沒有要求的權限,系統就會擲回 SecurityException 並拒絕存取。

要求權限

要求權限的目的是向執行階段通知您的應用程式需要哪些權限才能執行,以及確保它只會收到實際需要的權限。 例如,如果您的應用程式必須將資料寫入本機磁碟,它就需要 FileIOPermission。 如果系統沒有授與該權限,當此應用程式嘗試寫入磁碟時,它就會失敗。 不過,如果應用程式要求 FileIOPermission,但系統沒有授與該權限,則此應用程式一開始將產生例外狀況而且不會載入。

在應用程式僅需要從磁碟中讀取資料的情況下,您可以要求絕不授與任何寫入權限給應用程式。 在錯誤或惡意攻擊的事件中,您的程式碼無法破壞它所運作的資料。 如需詳細資訊,請參閱 要求權限

以角色為基礎的安全性和 CAS

同時實作以角色為基礎的安全性和程式碼存取安全性 (CAS) 可強化應用程式的整體安全性。 以角色為基礎的安全性可以根據 Windows 帳戶或自訂識別,將安全性主體的相關資訊提供給目前的執行緒。 此外,應用程式通常會根據使用者所提供的認證,提供對資料或資源的存取。 基本上,這類應用程式會檢查使用者的角色並根據這些角色提供資源存取。

以角色為基礎的安全性可讓某個元件在執行階段識別目前的使用者及其相關聯的角色。 然後,系統會使用 CAS 原則來對應這項資訊,以便判斷在執行階段授與的權限集合。 若為指定的應用程式定義域,主應用程式 (Host) 就可以變更預設的以角色為基礎安全性原則,並且設定預設的安全性主體,以便代表某位使用者以及與該使用相關聯的角色。

CLR 會使用某些權限來實作在 Managed 程式碼上強制執行限制的機制。 以角色為基礎的安全性權限會提供一項機制,讓您探索某位使用者 (或代表使用者運作的代理程式) 是否具有特定識別或屬於指定角色的成員。 如需詳細資訊,請參閱 安全性權限

根據您所建立的應用程式類型,您也應該考慮在資料庫中實作以角色為基礎的權限。 深入瞭解 L Server 角色型安全性,請參閱 SQL Server 安全性

組件

組件會構成 .NET Framework 應用程式之部署、版本控制、重複使用、啟動範圍和安全性權限的基本單位。 組件會提供針對一起運作而建立而且構成邏輯功能單位之類型和資源的組合。 對 CLR 而言,類型不會存在組件內容外部。 深入瞭解建立及部署組件,請參閱使用元件進行程式設計

強式命名組件

強式名稱 (Strong Name) 或數位簽章包含組件的識別,其中包括簡單文字名稱、版本號碼和文化特性資訊 (如果有提供的話),以及公開金鑰 (Public Key) 和數位簽章。 數位簽章是從使用對應之私密金鑰的組件檔中產生的。 組件檔包含組件資訊清單 (Assembly Manifest),其中包含組成組件之所有檔案的名稱和雜湊。

強式命名組件可提供應用程式或元件唯一的識別,讓其他軟體用來明確參考該應用程式或元件。 強式命名可保護組件,讓內含惡意程式碼的組件無法假冒該組件。 此外,強式命名還能確保不同元件版本之間的版本一致性。 您必須強式命名將部署到全域組件快取 (GAC) 的組件。 如需詳細資訊,請參閱建立和使用強式名稱的組件

ADO.NET 2.0 中的部分信任

在 ADO.NET 2.0 中,.NET Framework Data Provider for SQL Server、.NET Framework Data Provider for OLE DB、.NET Framework Data Provider for ODBC 和 .NET Framework Data Provider for Oracle 現在都可以在部分信任的環境中執行。 在舊版的 .NET Framework 中,只有 System.Data.SqlClient 才能在低於完全信任的應用程式中使用。

使用 SQL Server 提供者的部分信任應用程式至少必須具有執行和 SqlClientPermission 使用權限。

部分信任的使用權限屬性

在部分信任案例中,可使用 SqlClientPermissionAttribute 成員進一步限制 SQL Server 的 .NET Framework 資料提供者之可用功能。

下列表格列出可用的 SqlClientPermissionAttribute 屬性及其說明:

使用權限屬性 描述
Action 取得或設定安全性動作。 繼承自 SecurityAttribute
AllowBlankPassword 啟用或停用在連接字串中使用空白密碼。 有效值為 true (表示啟用空白密碼) 和 false (表示停用空白密碼)。 繼承自 DBDataPermissionAttribute
ConnectionString 識別允許的連接字串。 可識別多個連接字串。 注意:不要在連接字串中包含使用者 ID 或密碼。 在這個發行版本中,您無法使用 .NET Framework 組態工具變更連接字串限制。

繼承自 DBDataPermissionAttribute
KeyRestrictions 識別是否為允許的連接字串參數。 連接字串參數是會以 <parameter name>= 這樣的格式來識別。 也可以指定多個參數,只要以分號 (;) 將其分隔即可。 注意:如果您未指定 KeyRestrictions,但將 KeyRestrictionBehavior 屬性設定為 AllowOnlyPreventUsage,則不允許其他連接字串參數。 繼承自 DBDataPermissionAttribute
KeyRestrictionBehavior 將連接字串參數識別為唯一允許的其他參數 (AllowOnly),或識別不允許的其他參數 (PreventUsage)。 AllowOnly 是預設值。 繼承自 DBDataPermissionAttribute
TypeID 在衍生類別中實作時,取得唯一識別項。 繼承自 Attribute
Unrestricted 指示是否針對資源,宣告不受限的使用權限。 繼承自 SecurityAttribute

ConnectionString 語法

下列範例將示範如何使用組態檔的 connectionStrings 項目,只允許使用特定的連接字串。 深入瞭解從組態檔儲存及擷取連接字串,請參閱連接字串

<connectionStrings>  
  <add name="DatabaseConnection"
    connectionString="Data Source=(local);Initial
    Catalog=Northwind;Integrated Security=true;" />  
</connectionStrings>  

KeyRestrictions 語法

下列範例啟用相同的連接字串,同時也啟用 EncryptPacket Size 連接字串選項,但限制使用任何其他連接字串選項。

<connectionStrings>  
  <add name="DatabaseConnection"
    connectionString="Data Source=(local);Initial
    Catalog=Northwind;Integrated Security=true;"  
    KeyRestrictions="Encrypt=;Packet Size=;"  
    KeyRestrictionBehavior="AllowOnly" />  
</connectionStrings>  

含 PreventUsage 的 KeyRestrictionBehavior 語法

下列範例將啟用相同的連接字串,並允許 User IdPasswordPersist Security Info 以外的其他所有連接參數。

<connectionStrings>  
  <add name="DatabaseConnection"
    connectionString="Data Source=(local);Initial
    Catalog=Northwind;Integrated Security=true;"  
    KeyRestrictions="User Id=;Password=;Persist Security Info=;"  
    KeyRestrictionBehavior="PreventUsage" />  
</connectionStrings>  

含 AllowOnly 的 KeyRestrictionBehavior 語法

下列範例啟用兩個連接字串,這兩個連接字串同時包含 Initial CatalogConnection TimeoutEncryptPacket Size 參數。 所有其他的連接字串參數則都限制使用。

<connectionStrings>  
  <add name="DatabaseConnection"
    connectionString="Data Source=(local);Initial
    Catalog=Northwind;Integrated Security=true;"  
    KeyRestrictions="Initial Catalog;Connection Timeout=;  
       Encrypt=;Packet Size=;"
    KeyRestrictionBehavior="AllowOnly" />  
  
  <add name="DatabaseConnection2"
    connectionString="Data Source=SqlServer2;Initial
    Catalog=Northwind2;Integrated Security=true;"  
    KeyRestrictions="Initial Catalog;Connection Timeout=;  
       Encrypt=;Packet Size=;"
    KeyRestrictionBehavior="AllowOnly" />  
</connectionStrings>  

以自訂的使用權限集啟用部份信任

若要啟用特定區域的 System.Data.SqlClient 使用權限,系統管理員必須建立自訂的使用權限集合,並將其設定為特定區域的使用權限集合。 不可修改預設的使用權限集合 (例如 LocalIntranet)。 例如,若要併入具有 ZoneLocalIntranet 的程式碼的 System.Data.SqlClient 使用權限,系統管理員可以複製 LocalIntranet 的使用權限集合、將它重新命名為 「CustomLocalIntranet」、加入 System.Data.SqlClient 使用權限、使用 Caspol.exe (程式碼存取安全性原則工具) 匯入 CustomLocalIntranet 使用權限集合,然後將 LocalIntranet_Zone 的使用權限集合設為 CustomLocalIntranet。

使用權限集合範例

下列是部分受信任案例中的「SQL Server 的 .NET Framework 資料提供者」使用權限集合範例。 深入瞭解建立自訂權限集合,請參閱使用 Caspol.exe 設定權限集合

<PermissionSet class="System.Security.NamedPermissionSet"  
  version="1"  
  Name="CustomLocalIntranet"  
  Description="Custom permission set given to applications on  
    the local intranet">  
  
<IPermission class="System.Data.SqlClient.SqlClientPermission, System.Data, Version=2.0.0000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"  
version="1"  
AllowBlankPassword="False">  
<add ConnectionString="Data Source=(local);Integrated Security=true;"  
 KeyRestrictions="Initial Catalog=;Connection Timeout=;  
   Encrypt=;Packet Size=;"
 KeyRestrictionBehavior="AllowOnly" />  
 </IPermission>  
</PermissionSet>  

使用安全性使用權限驗證 ADO.NET 程式碼存取

若為部分信任案例,則可指定 SqlClientPermissionAttribute,藉以取得程式碼中之特定方法的 CAS 權限。 如果生效的限制安全性原則不允許該權限,則在執行程式碼前會擲回例外狀況。 深入瞭解安全性原則,請參閱 安全性原則管理安全性原則最佳做法

範例

下列範例示範如何撰寫需要特定連接字串的程式碼。 它模擬如何拒絕 System.Data.SqlClient 的不受限權限,而系統管理員在實務上會使用 CAS 原則來實作這些權限。

重要

設計 ADO.NET 的 CAS 權限時,最正確的模式是以最大的限制 (完全沒有權限) 開始,然後加入程式碼必須執行之特殊工作所需的特定權限。 相反的模式 (以所有權限開始,然後再拒絕特定權限) 並不安全,因為有許多方法可表示相同的連接字串。 例如,如果您以所有權限開始,然後嘗試拒絕連接字串 "server=someserver" 的用法,您仍可使用 "server=someserver.mycompany.com" 字串。 只要以不授與任何權限開始,您就能減少權限集合具有漏洞的機會。

下列程式碼將示範 SqlClient 如何執行安全性需求,也就是在沒有適當的 CAS 權限時,擲回 SecurityException。 主控台視窗會顯示 SecurityException 輸出。

using System;
using System.Data;
using System.Data.SqlClient;
using System.Security;
using System.Security.Permissions;

namespace PartialTrustTopic {
   public class PartialTrustHelper : MarshalByRefObject {
      public void TestConnectionOpen(string connectionString) {
         // Try to open a connection.
         using (SqlConnection connection = new SqlConnection(connectionString)) {
            connection.Open();
         }
      }
   }

   class Program {
      static void Main(string[] args) {
         TestCAS("Data Source=(local);Integrated Security=true", "Data Source=(local);Integrated Security=true;Initial Catalog=Test");
      }

      static void TestCAS(string connectString1, string connectString2) {
         // Create permission set for sandbox AppDomain.
         // This example only allows execution.
         PermissionSet permissions = new PermissionSet(PermissionState.None);
         permissions.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));

            // Create sandbox AppDomain with permission set that only allows execution,
            // and has no SqlClientPermissions.
            AppDomainSetup appDomainSetup = new AppDomainSetup();
         appDomainSetup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
         AppDomain firstDomain = AppDomain.CreateDomain("NoSqlPermissions", null, appDomainSetup, permissions);

         // Create helper object in sandbox AppDomain so that code can be executed in that AppDomain.
         Type helperType = typeof(PartialTrustHelper);
         PartialTrustHelper firstHelper = (PartialTrustHelper)firstDomain.CreateInstanceAndUnwrap(helperType.Assembly.FullName, helperType.FullName);

         try {
            // Attempt to open a connection in the sandbox AppDomain.
            // This is expected to fail.
            firstHelper.TestConnectionOpen(connectString1);
            Console.WriteLine("Connection opened, unexpected.");
         }
         catch (System.Security.SecurityException ex) {
            Console.WriteLine("Failed, as expected: {0}",
                ex.FirstPermissionThatFailed);

            // Uncomment the following line to see Exception details.
            // Console.WriteLine("BaseException: " + ex.GetBaseException());
         }

         // Add permission for a specific connection string.
         SqlClientPermission sqlPermission = new SqlClientPermission(PermissionState.None);
         sqlPermission.Add(connectString1, "", KeyRestrictionBehavior.AllowOnly);

         permissions.AddPermission(sqlPermission);

         AppDomain secondDomain = AppDomain.CreateDomain("OneSqlPermission", null, appDomainSetup, permissions);
         PartialTrustHelper secondHelper = (PartialTrustHelper)secondDomain.CreateInstanceAndUnwrap(helperType.Assembly.FullName, helperType.FullName);

         // Try connection open again, it should succeed now.
         try {
            secondHelper.TestConnectionOpen(connectString1);
            Console.WriteLine("Connection opened, as expected.");
         }
         catch (System.Security.SecurityException ex) {
            Console.WriteLine("Unexpected failure: {0}", ex.Message);
         }

         // Try a different connection string. This should fail.
         try {
            secondHelper.TestConnectionOpen(connectString2);
            Console.WriteLine("Connection opened, unexpected.");
         }
         catch (System.Security.SecurityException ex) {
            Console.WriteLine("Failed, as expected: {0}", ex.Message);
         }
      }
   }
}
Imports System.Data
Imports System.Data.SqlClient
Imports System.Security
Imports System.Security.Permissions

Namespace PartialTrustTopic
    Public Class PartialTrustHelper
        Inherits MarshalByRefObject
        Public Sub TestConnectionOpen(ByVal connectionString As String)
            ' Try to open a connection.
            Using connection As New SqlConnection(connectionString)
                connection.Open()
            End Using
        End Sub
    End Class

    Class Program
        Public Shared Sub Main(ByVal args As String())
            TestCAS("Data Source=(local);Integrated Security=true", "Data Source=(local);Integrated Security=true;Initial Catalog=Test")
        End Sub

        Public Shared Sub TestCAS(ByVal connectString1 As String, ByVal connectString2 As String)
            ' Create permission set for sandbox AppDomain.
            ' This example only allows execution.
            Dim permissions As New PermissionSet(PermissionState.None)
            permissions.AddPermission(New SecurityPermission(SecurityPermissionFlag.Execution))

            ' Create sandbox AppDomain with permission set that only allows execution,
            ' and has no SqlClientPermissions.
            Dim appDomainSetup As New AppDomainSetup()
            appDomainSetup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase
            Dim firstDomain As AppDomain = AppDomain.CreateDomain("NoSqlPermissions", Nothing, appDomainSetup, permissions)

            ' Create helper object in sandbox AppDomain so that code can be executed in that AppDomain.
            Dim helperType As Type = GetType(PartialTrustHelper)
            Dim firstHelper As PartialTrustHelper = DirectCast(firstDomain.CreateInstanceAndUnwrap(helperType.Assembly.FullName, helperType.FullName), PartialTrustHelper)

            Try
                ' Attempt to open a connection in the sandbox AppDomain.
                ' This is expected to fail.
                firstHelper.TestConnectionOpen(connectString1)
                Console.WriteLine("Connection opened, unexpected.")
            Catch ex As System.Security.SecurityException

                ' Uncomment the following line to see Exception details.
                ' Console.WriteLine("BaseException: " + ex.GetBaseException());
                Console.WriteLine("Failed, as expected: {0}", ex.FirstPermissionThatFailed)
            End Try

            ' Add permission for a specific connection string.
            Dim sqlPermission As New SqlClientPermission(PermissionState.None)
            sqlPermission.Add(connectString1, "", KeyRestrictionBehavior.AllowOnly)

            permissions.AddPermission(sqlPermission)

            Dim secondDomain As AppDomain = AppDomain.CreateDomain("OneSqlPermission", Nothing, appDomainSetup, permissions)
            Dim secondHelper As PartialTrustHelper = DirectCast(secondDomain.CreateInstanceAndUnwrap(helperType.Assembly.FullName, helperType.FullName), PartialTrustHelper)

            ' Try connection open again, it should succeed now.
            Try
                secondHelper.TestConnectionOpen(connectString1)
                Console.WriteLine("Connection opened, as expected.")
            Catch ex As System.Security.SecurityException
                Console.WriteLine("Unexpected failure: {0}", ex.Message)
            End Try

            ' Try a different connection string. This should fail.
            Try
                secondHelper.TestConnectionOpen(connectString2)
                Console.WriteLine("Connection opened, unexpected.")
            Catch ex As System.Security.SecurityException
                Console.WriteLine("Failed, as expected: {0}", ex.Message)
            End Try
        End Sub
    End Class
End Namespace

您應該在 [主控台] 視窗中看到以下輸出:

Failed, as expected: <IPermission class="System.Data.SqlClient.  
SqlClientPermission, System.Data, Version=2.0.0.0,
  Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1"  
  AllowBlankPassword="False">  
<add ConnectionString="Data Source=(local);Initial Catalog=  
  Northwind;Integrated Security=SSPI" KeyRestrictions=""  
KeyRestrictionBehavior="AllowOnly"/>  
</IPermission>  
  
Connection opened, as expected.  
Failed, as expected: Request failed.  

與 Unmanaged 程式碼的互通性

在 CLR 外部執行的程式碼稱為 Unmanaged 程式碼。 因此,CAS 等安全性機制無法套用至 Unmanaged 程式碼。 COM 元件、ActiveX 介面及 Windows API 函式都是 Unmanaged 程式碼的範例。 執行 Unmanaged 程式碼時,您應該套用特殊安全性考量,以免危及整體應用程式安全性。 如需詳細資訊,請參閱與 Unmanaged 程式碼互通

.NET Framework 也透過 COM Interop 提供存取,藉以支援現有 COM 元件的回溯相容性 (Backward Compatibility)。 您可以使用 COM Interop 工具來匯入相關的 COM 型別,以便將 COM 元件併入 .NET Framework 應用程式中。 一旦匯入之後,COM 型別便可供使用。 COM Interop 也會將組件中繼資料匯出至型別程式庫並將 Managed 元件註冊為 COM 元件,藉以讓 COM 用戶端存取 Managed 程式碼。 如需詳細資訊,請參閱 進階 COM 互通性

另請參閱