다음을 통해 공유


보안 최적화

일부 응용 프로그램에서는 보안 검사로 인한 성능 문제가 발생할 수 있습니다. 성능을 향상시키는 데 사용할 수 있는 최적화 방법에는 두 가지가 있습니다. 한 가지 방법은 보안 요청을 결합하는 방법이고, 다른 한 가지 방법은 비관리 코드에 대한 호출 권한 요청을 숨기는 방법입니다. 이 두 방법을 사용하면 응용 프로그램의 성능을 향상시킬 수 있지만 응용 프로그램을 보안 위험에 노출시킬 수도 있습니다. 따라서 이러한 최적화 방법을 사용하기 전에 다음과 같은 예방 조치를 취해야 합니다.

  • 관리 코드의 경우 보안 코딩 지침을 따릅니다.

  • 최적화의 보안 관련 문제를 이해하고 응용 프로그램 보호에 적절한 다른 방법을 사용합니다.

  • 응용 프로그램 성능을 향상시키는 데 필요한 최소한의 보안 최적화를 구현합니다.

코드를 최적화한 후에는 최적화된 코드를 테스트해서 성능이 실제로 향상되었는지 확인해야 합니다. 성능이 향상되지 않았으면 보안 최적화를 제거하여 실수로 발생한 보안 약점을 없애야 합니다.

주의 정보주의

보안 최적화를 위해서는 표준 코드 액세스 보안을 변경해야 합니다.코드가 보안 위험에 노출되는 것을 막기 위해서는 최적화 방법을 사용하기 전에 최적화 방법의 보안 관련 문제를 이해해야 합니다.

보안 요청 결합

보안 요청을 하는 코드를 최적화하기 위해 요청을 결합하는 기술을 사용할 수 있는 경우가 있습니다.

예를 들면 다음과 같습니다.

  • 코드의 단일 메서드 내에서 여러 작업을 수행하는 경우.

  • 이러한 각각의 작업을 수행하는 동안, 호출할 때마다 동일한 권한을 요청하는 관리되는 클래스 라이브러리를 코드에서 호출하는 경우.

이와 같은 경우

  • 해당 권한의 AssertDemand를 수행하도록 코드를 수정하여 보안 요청에 의해 발생하는 오버헤드를 줄일 수 있습니다.

메서드 위의 호출 스택의 깊이가 깊으면 이 기술을 사용하여 성능을 향상시킬 수 있습니다.

이러한 작업이 수행되는 방법을 설명하기 위해 메서드 M이 100개의 작업을 수행한다고 가정합니다. 각 작업은 코드와 코드의 모든 호출자에게 X 권한을 요청하는 보안 요청을 하는 라이브러리를 호출합니다. 보안 요청 때문에, 각 작업을 수행하면 공용 언어 런타임은 각 호출자의 권한을 검사하기 위해 전제 호출 스택을 탐색하여 실제로 X 권한이 각 호출자에게 부여되었는지 파악합니다. M 메서드 위의 호출 스택의 깊이가 n 수준이면 100n번의 비교가 필요합니다.

최적화하기 위해 메서드 M에서 다음 작업을 수행할 수 있습니다.

  • X를 요청합니다. 이 요청에 의해 공용 언어 런타임은 모든 호출자에게 실제로 권한 X가 있는지 확인하는 스택 워크(깊이 n)를 수행합니다.

  • 그런 다음, 권한 X를 어설션합니다. 그러면 스택 워크가 메서드 M에서 중단되어 성공하게 되며 권한 비교 횟수가 99n번 줄어듭니다.

다음 코드 예제에서 GetFileCreationTime 메서드는 디렉터리의 문자열 표현을 매개 변수로 사용하여 이 디렉터리에 있는 모든 파일의 이름과 만든 날짜를 표시합니다. 정적 File.GetCreationTime 메서드는 이 파일에서 정보를 읽지만, 읽는 모든 파일에 대해 요청과 스택 워크를 필요로 합니다. 이 메서드는 FileIOPermission 개체의 새 인스턴스를 만들며 스택에 있는 모든 호출자의 권한을 검사하는 요청을 수행하여 요청이 성공하면 권한을 어설션합니다. 이 요청이 성공하면 단 하나의 스택 워크만 수행되며 메서드는 전달된 디렉터리에 있는 모든 파일에서 만든 시간을 읽습니다.

using System;
using System.IO;
using System.Security;
using System.Security.Permissions;

namespace OptimizedSecurity
{
   public class FileUtil
   {
      public FileUtil()
      {
      }

      public void GetFileCreationTime(string Directory)
      {
         //Initialize DirectoryInfo object to the passed directory. 
         DirectoryInfo DirFiles = new DirectoryInfo(Directory);

         //Create a DateTime object to be initialized below.
         DateTime TheTime;

         //Get a list of files for the current directory.
         FileInfo[] Files = DirFiles.GetFiles();
         
         //Create a new instance of FileIOPermission with read 
         //permission to the current directory.
         FileIOPermission FilePermission = new FileIOPermission(FileIOPermissionAccess.Read, Directory);

         try
         {
            //Check the stack by making a demand.
            FilePermission.Demand();

            //If the demand succeeded, assert permission and 
            //perform the operation.
            FilePermission.Assert();

            for(int x = 0 ; x<= Files.Length -1 ; x++)
            {
               TheTime = File.GetCreationTime(Files[x].FullName);
               Console.WriteLine("File: {0} Created: {1:G}", Files[x].Name,TheTime );
            }
            // Revert the Assert when the operation is complete.
            CodeAccessPermission.RevertAssert();
         }
         //Catch a security exception and display an error.
         catch(SecurityException)
         {
            Console.WriteLine("You do not have permission to read this directory.");
         }                            
      }
   }
}
Option Explicit
Option Strict
Imports System
Imports System.IO
Imports System.Security
Imports System.Security.Permissions
Namespace OptimizedSecurity
   Public Class FileUtil      
      Public Sub New()
      End Sub
      Public Sub GetFileCreationTime(directory As String)
         'Initialize DirectoryInfo object to the passed directory. 
         Dim dirFiles As New DirectoryInfo(directory)
         'Create a DateTime object to be initialized below.
         Dim theTime As DateTime
         'Get a list of files for the current directory.
         Dim files As FileInfo() = dirFiles.GetFiles()
         'Create a new instance of FileIOPermission with read 
         'permission to the current directory.
         Dim filePermission As New FileIOPermission(FileIOPermissionAccess.Read, Directory)
         Try
            'Check the stack by making a demand.
            filePermission.Demand()
            'If the demand succeeded, assert permission and 
            'perform the operation.
            filePermission.Assert()
            Dim x As Integer
            For x = 0 To Files.Length - 1
               theTime = file.GetCreationTime(files(x).FullName)
               Console.WriteLine("File: {0} Created: {1:G}", files(x).Name, theTime)
            Next x
            ' Revert the Assert when the operation is complete.
            CodeAccessPermission.RevertAssert()
         'Catch a security exception and display an error.
         Catch
            Console.WriteLine("You do not have permission to read this directory.")
         End Try
      End Sub
   End Class
End Namespace

앞의 예제에서 요청이 성공하면 전달된 디렉터리의 모든 파일과 파일을 만든 날짜 및 시간이 표시됩니다. 요청이 실패하면 보안 예외를 가로채서 다음과 같은 메시지를 콘솔에 표시합니다.

You do not have permission to read this directory.

비관리 코드 권한에 대한 요청 숨기기

비관리 코드를 호출하는 권한을 갖는 코드에 대해 특별한 최적화를 사용할 수 있습니다. 이러한 최적화를 통해 관리 코드는 스택 워크 오버헤드를 발생시키지 않고 비관리 코드를 호출할 수 있습니다. 비관리 코드 권한을 어설션하면 스택 워크가 줄어들지만, 여기서 설명하는 최적화를 통해 스택 워크를 완전히 없앨 수도 있습니다. 비관리 코드를 호출하는 권한에 대한 자세한 내용은 SecurityPermission을 참조하십시오.

일반적으로, 비관리 코드를 호출하면 비관리 코드 권한 요청이 발생합니다. 이에 따라 비관리 코드를 호출하는 권한이 모든 호출자에게 있는지 파악하는 스택 워크가 수행됩니다. 사용자 지정 특성 SuppressUnmanagedCodeSecurityAttribute를 비관리 코드를 호출하는 메서드에 적용하면 요청이 숨겨집니다. 이 특성은 런타임에 수행되는 전체 스택 워크를 링크 시간에 수행되는 직접 실행 호출자의 권한 검사로 대체합니다. 사실, 이 특성을 사용하면 비관리 코드에 대해 보안상 허점이 생깁니다. 비관리 코드 권한을 갖는 코드만이 이 특성을 사용할 수 있습니다. 다른 경우에 사용하면 이 특성은 아무 효력이 없습니다.

주의 정보주의

SuppressUnmanagedCodeSecurityAttribute 특성은 아주 신중하게 사용해야 합니다.이 특성을 잘못 사용하면 보안상 허점이 생길 수 있습니다.SuppressUnmanagedCodeSecurityAttribute 특성을 사용하여 비관리 코드를 호출하는 신뢰 수준이 낮은 코드(비관리 코드 권한이 없는 코드)를 허용할 수 없습니다.

이 특성은 전용으로 선언된, 비관리 코드의 진입점에 가장 잘 적용되므로 다른 어셈블리의 코드는 보안 숨기기의 장점을 활용하거나 액세스할 수 없습니다. 일반적으로 이 특성을 사용하는 신뢰 수준이 아주 높은 관리 코드는 호출자를 대신해서 비관리 코드를 호출하기 전에 먼저 호출자의 권한을 요청합니다.

다음 예제는 전용 진입점에 적용된 SuppressUnmanagedCodeSecurityAttribute 특성을 보여 줍니다.

<SuppressUnmanagedCodeSecurityAttribute()> Private Declare Sub 
EntryPoint Lib "some.dll"(args As String)
[SuppressUnmanagedCodeSecurityAttribute()]
[DllImport("some.dll")]
private static extern void EntryPoint(string args);

드문 경우지만, 비관리 코드가 가능한 모든 환경에서 완벽하게 안전한 경우 SuppressUnmanagedCodeSecurityAttribute 특성이 있는 메서드는 이 특성을 private이 아니라 public으로 지정하여 다른 관리 코드에 직접 노출시킬 수 있습니다. SuppressUnmanagedCodeSecurityAttribute 특성이 있는 메서드를 노출시키려면 비관리 코드의 기능이 안전해야 할 뿐 아니라 악의적인 호출자의 공격을 견뎌낼 수 있어야 합니다. 예를 들어, 인수가 위조되어 코드 오동작을 일으키는 경우에도 코드는 적절하게 작동되어야 합니다.

선언적 재정의 및 명령적 요청 사용

어설션 및 다른 재정의는 선언적을 사용할 때 가장 빠르며 요청은 명령적으로 사용할 때 가장 빠릅니다. 성능이 급격하게 향상되지는 않지만 선언적 재정의와 명령적 요청을 사용하면 코드 성능을 향상시킬 수 있습니다.

참고 항목

참조

File.GetCreationTime

SecurityPermission

SuppressUnmanagedCodeSecurityAttribute

개념

코드 액세스 보안

보안 클래스 라이브러리 작성