클라이언트 가장

사용자 응용 프로그램이 WMI 공급자를 통해 시스템의 개체에서 데이터를 요청하는 경우 가장은 공급자가 아닌 클라이언트 보안 수준을 나타내는 자격 증명을 표시한다는 것을 의미합니다. 가장은 클라이언트가 시스템에 대한 정보에 대한 무단 액세스를 얻을 수 없도록 합니다.

이 항목에서 다루는 섹션은 다음과 같습니다.

WMI는 일반적으로 LocalServer 보안 컨텍스트를 사용하여 높은 보안 수준에서 관리 서비스로 실행됩니다. 관리 서비스를 사용하면 WMI가 권한 있는 정보에 액세스할 수 있는 수단을 제공합니다. 정보를 위해 공급자를 호출할 때 WMI는 해당 SID(보안 식별자)를 공급자에게 전달해 공급자가 동일한 높은 보안 수준에서 정보에 액세스할 수 있도록 합니다.

WMI 응용 프로그램 시작 프로세스 중에 Windows 운영 체제는 WMI 응용 프로그램에 프로세스를 시작한 사용자의 보안 컨텍스트를 제공합니다. 사용자의 보안 컨텍스트는 일반적으로 LocalServer보다 낮은 보안 수준이므로 사용자에게 WMI에서 사용할 수 있는 모든 정보에 액세스할 수 있는 권한이 없을 수 있습니다. 사용자 응용 프로그램에서 동적 정보를 요청하면 WMI는 사용자의 SID를 해당 공급자에 전달합니다. 적절하게 작성된 경우 공급자는 공급자 SID가 아닌 사용자 SID를 사용하여 정보에 액세스하려고 시도합니다.

공급자가 클라이언트 응용 프로그램을 가장하려면 클라이언트 응용 프로그램 및 공급자가 다음 조건을 충족해야 합니다.

  • 클라이언트 응용 프로그램은 COM 연결 보안 수준의 RPC_C_IMP_LEVEL_IMPERSONATE 또는 RPC_C_IMP_LEVEL_DELEGATE를 사용하여 WMI를 호출해야 합니다. 자세한 내용은 WMI 보안 유지 관리를 참조하세요.
  • 공급자는 WMI에 가장 공급자로 등록해야 합니다. 자세한 내용은 가장을 위한 공급자 등록을 참조하세요.
  • 공급자는 권한 있는 정보에 액세스하기 전에 클라이언트 응용 프로그램의 보안 수준으로 전환해야 합니다. 자세한 내용은 공급자 내에서 가장 수준 설정을 참조하세요.
  • 이 정보에 대한 액세스가 거부되면 공급자가 오류 조건을 올바르게 처리해야 합니다. 자세한 내용은 공급자에서 액세스 거부 메시지 처리를 참조하세요.

가장을 위한 공급자 등록

WMI는 가장 공급자로 등록된 공급자에게만 클라이언트 응용 프로그램의 SID를 전달합니다. 공급자가 가장을 수행하도록 설정하려면 공급자 등록 프로세스를 수정해야 합니다.

다음 절차에서는 가장을 위한 공급자를 등록하는 방법을 설명합니다. 이 절차에서는 등록 프로세스를 이미 이해하고 있다고 가정합니다. 등록 프로세스에 대한 자세한 내용은 공급자 등록을 참조하세요.

가장을 위한 공급자 등록하기

  1. 공급자를 나타내는 __Win32Provider 클래스의 ImpersonationLevel 속성을 1로 설정합니다.

    ImpersonationLevel 속성은 공급자가 가장을 지원하는지 여부를 문서화합니다. ImpersonationLevel을 0으로 설정하면 공급자가 클라이언트를 가장하지 않고 WMI와 동일한 사용자 컨텍스트에서 요청된 모든 작업을 수행합니다. ImpersonationLevel을 1로 설정하면 공급자가 가장 호출을 사용하여 클라이언트를 대신하여 수행된 작업을 확인합니다.

  2. 동일한 __Win32Provider 클래스의 PerUserInitialization 속성을 TRUE로 설정합니다.

참고

__Win32Provider 속성 InitializeAsAdminFirstTRUE로 설정된 공급자를 등록하는 경우 공급자는 초기화 단계에서만 관리 수준 스레드 보안 토큰을 사용합니다. CoImpersonateClient에 대한 호출은 실패하지 않지만 공급자는 클라이언트가 아닌 WMI의 보안 컨텍스트를 사용합니다.

 

다음 코드 예제에서는 가장을 위해 공급자를 등록하는 방법을 보여줍니다.

instance of __Win32Provider
{
    CLSID = "{FD4F53E0-65DC-11d1-AB64-00C04FD9159E}";
    ImpersonationLevel = 1;
    Name = "MS_NT_EVENTLOG_PROVIDER";
    PerUserInitialization = TRUE;
};

공급자 내에서 가장 수준 설정

__Win32Provider 클래스 속성 ImpersonationLevel이 1로 설정된 공급자를 등록하는 경우 WMI는 공급자를 호출하여 다양한 클라이언트를 가장합니다. 이러한 호출을 처리하려면 IWbemServices 인터페이스 구현에서 CoImpersonateClientCoRevertToSelf COM 함수를 사용합니다.

CoImpersonateClient 함수를 사용하면 서버가 호출한 클라이언트를 가장할 수 있습니다. CoImpersonateClientIWbemServices 구현에 호출하면 공급자가 클라이언트의 스레드 토큰과 일치하도록 공급자의 스레드 토큰을 설정하여 클라이언트를 가장할 수 있습니다. CoImpersonateClient를 호출하지 않으면 공급자가 관리자 수준의 보안에서 코드를 실행하여 잠재적인 보안 취약성을 만듭니다. 공급자가 일시적으로 관리자 역할을 하거나 액세스 검사를 수동으로 수행해야 하는 경우 CoRevertToSelf를 호출합니다.

CoImpersonateClient와 달리 CoRevertToSelf는 스레드 가장 수준을 처리하는 COM 함수입니다. 이 경우 CoRevertToSelf는 가장 수준을 원래 가장 설정으로 다시 변경합니다. 일반적으로 공급자는 처음에 관리자이며 호출자 또는 자체 호출을 나타내는 호출을 만드는지 여부에 따라 CoImpersonateClientCoRevertToSelf를 번갈아 사용합니다. 최종 사용자에게 보안 허점을 노출하지 않도록 이러한 호출을 올바르게 배치하는 것은 공급자의 책임입니다. 예를 들어 공급자는 가장 코드 시퀀스 내에서 네이티브 Windows 함수만 호출해야 합니다.

참고

CoImpersonateClientCoRevertToSelf의 목적은 공급자에 대한 보안을 설정하는 것입니다. 가장에 실패한 것으로 확인되면 IWbemObjectSink::SetStatus를 통해 적절한 완성 코드를 WMI에 반환해야 합니다. 자세한 내용은 공급자에서 액세스 거부 메시지 처리를 참조하세요.

 

공급자에서 보안 수준 유지 관리

공급자는 IWbemServices 구현에서 CoImpersonateClient를 한 번 호출할 수 없으며 공급자 기간 동안 가장 자격 증명이 그대로 유지된다고 가정합니다. 대신 구현 과정에서 CoImpersonateClient를 여러 번 호출하여 WMI가 자격 증명을 변경하지 못하도록 합니다.

공급자에 대한 가장 설정의 주요 관심사는 재입력입니다. 이 컨텍스트에서 재입력은 공급자가 WMI에 정보를 호출하고 WMI가 공급자로 다시 호출될 때까지 대기하는 경우입니다. 기본적으로 실행 스레드는 나중에 코드를 다시 입력하기 위해 공급자 코드를 남깁니다. 재입력은 COM 디자인의 일부이며 일반적으로 문제가 되지 않습니다. 그러나 실행 스레드가 WMI에 들어가면 스레드는 WMI의 가장 수준을 사용합니다. 스레드가 공급자로 돌아오면 CoImpersonateClient에 대한 다른 호출을 사용하여 가장 수준을 다시 설정해야 합니다.

공급자의 보안 허점으로부터 자신을 보호하려면 클라이언트를 가장하는 동안에만 WMI에 대한 재입력 호출을 수행해야 합니다. 즉 CoImpersonateClient를 호출한 후 CoRevertToSelf를 호출하기 전에 WMI를 호출해야 합니다. CoRevertToSelf로 인해 가장이 실행 중인 사용자 수준 WMI로 설정되므로 일반적으로 LocalSystem, CoRevertToSelf를 호출한 후 WMI에 대한 재입력 호출은 사용자에게 제공될 수 있으며, 호출된 공급자는 예상보다 훨씬 더 많은 기능을 제공할 수 있습니다.

참고

시스템 함수 또는 다른 인터페이스 메서드를 호출하는 경우 호출 컨텍스트가 유지 관리되지 않습니다.

 

공급자에서 액세스 거부 메시지 처리

대부분의 액세스 거부 오류 메시지는 클라이언트가 액세스 권한이 없는 클래스 또는 정보를 요청할 때 표시됩니다. 공급자가 액세스 거부 오류 메시지를 WMI에 반환하고 WMI가 이를 클라이언트에 전달하는 경우 클라이언트는 정보가 있음을 유추할 수 있습니다. 경우에 따라 이는 보안 위반이 될 수 있습니다. 따라서 공급자가 클라이언트에 메시지를 전파해서는 안 됩니다. 대신 공급자가 제공한 클래스 집합은 노출되지 않아야 합니다. 마찬가지로 동적 인스턴스 공급자는 기본 데이터 원본을 호출하여 액세스 거부 메시지를 처리하는 방법을 결정해야 합니다. 이 철학을 WMI 환경에 복제하는 것은 공급자의 책임입니다. 자세한 내용은 부분 인스턴스 보고부분 열거형 보고를 참조하세요.

공급자가 액세스 거부 메시지를 처리하는 방법을 결정할 때 코드를 작성하고 디버그해야 합니다. 디버깅하는 동안 낮은 가장으로 인한 거부와 코드 오류로 인한 거부를 구분하는 것이 편리한 경우가 많습니다. 코드에서 간단한 테스트를 사용하여 차이점을 확인할 수 있습니다. 자세한 내용은 액세스 거부 코드 디버깅을 참조하세요.

부분 인스턴스 보고

액세스 거부 메시지의 한 가지 일반적인 발생은 WMI가 인스턴스를 채우기 위해 모든 정보를 제공할 수 없는 경우입니다. 예를 들어 클라이언트는 하드 디스크 드라이브 개체를 볼 권한이 있지만 하드 디스크 드라이브 자체에서 사용할 수 있는 공간을 확인할 권한이 없을 수 있습니다. 공급자는 액세스 위반으로 인해 공급자가 인스턴스를 속성으로 완전히 채울 수 없는 상황을 처리하는 방법을 결정해야 합니다.

WMI는 인스턴스에 부분적으로 액세스할 수 있는 클라이언트에 대한 단일 응답이 필요하지 않습니다. 대신 WMI 버전 1.x를 사용하면 공급자가 다음 옵션 중 하나를 수행할 수 있습니다.

  • WBEM_E_ACCESS_DENIED를 사용하여 전체 작업에 실패하고 인스턴스를 반환하지 않습니다.

    WBEM_E_ACCESS_DENIED와 함께 오류 개체를 반환하여 거부 이유를 설명합니다.

  • 사용 가능한 모든 속성을 반환하고 사용할 수 없는 속성을 NULL로 채웁니다.

참고

WBEM_E_ACCESS_DENIED를 반환해도 엔터프라이즈에 보안 허점이 만들어지지 않는지 확인합니다.

 

부분 열거형 보고

액세스 위반의 또 다른 일반적인 발생은 WMI가 모든 열거형을 반환할 수 없는 경우입니다. 예를 들어 클라이언트는 모든 로컬 네트워크 컴퓨터 개체를 볼 수 있는 액세스 권한이 있지만 도메인 외부에서 컴퓨터 개체를 볼 수 있는 액세스 권한이 없을 수 있습니다. 공급자는 액세스 위반으로 인해 열거를 완료할 수 없는 상황을 처리하는 방법을 결정해야 합니다.

인스턴스 공급자와 마찬가지로 WMI는 부분 열거형에 대한 단일 응답이 필요하지 않습니다. 대신 WMI 버전 1.x를 사용하면 공급자가 다음 옵션 중 하나를 수행할 수 있습니다.

  • 공급자가 액세스할 수 있는 모든 인스턴스에 대한 WBEM_S_NO_ERROR를 반환합니다.

    이 옵션을 사용하는 경우 사용자는 일부 인스턴스를 사용할 수 없다는 것을 인식하지 않습니다. 행 수준 보안이 있는 SQL(구조적 쿼리 언어)을 사용하는 공급자와 같은 여러 공급자는 호출자의 보안 수준을 사용하여 결과 집합을 정의하는 데 성공한 부분 결과를 반환합니다.

  • WBEM_E_ACCESS_DENIED를 사용하여 전체 작업에 실패하고 인스턴스를 반환하지 않습니다.

    공급자는 필요에 따라 클라이언트에 상황을 설명하는 오류 개체를 포함할 수 있습니다. 일부 공급자는 데이터 원본에 직렬로 액세스할 수 있으며 열거형을 통해 부분적으로 거부될 때까지 거부되지 않을 수 있습니다.

  • 액세스할 수 있지만 nonerror 상태 코드 WBEM_S_ACCESS_DENIED를 반환할 수 있는 모든 인스턴스를 반환합니다.

    공급자는 열거 중에 거부를 기록해야 하며 인스턴스를 계속 제공하여 nonerror 상태 코드로 마무리할 수 있습니다. 공급자는 첫 번째 거부 시 열거형을 종료하도록 선택할 수도 있습니다. 이 옵션의 근거는 공급자가 서로 다른 검색 패러다임을 가지고 있다는 것입니다. 공급자가 액세스 위반을 검색하기 전에 이미 인스턴스를 전달했을 수 있습니다. 일부 공급자는 다른 인스턴스를 계속 제공하도록 선택할 수 있으며 다른 공급자는 종료를 원할 수 있습니다.

COM의 구조로 인해 오류 개체를 제외하고 오류 중에 정보를 다시 마샬링할 수 없습니다. 따라서 정보와 오류 코드를 모두 반환할 수 없습니다. 정보를 반환하도록 선택하는 경우 nonerror 상태 코드를 대신 사용해야 합니다.

액세스 거부 코드 디버깅

일부 응용 프로그램은 RPC_C_IMP_LEVEL_IMPERSONATE 미만의 가장 수준을 사용할 수 있습니다. 이 경우 클라이언트 응용 프로그램에 대한 공급자가 수행한 대부분의 가장 호출이 실패합니다. 공급자를 성공적으로 설계하고 구현하려면 이 아이디어를 염두에 두어야 합니다.

기본적으로 공급자에 액세스할 수 있는 유일한 다른 수준의 가장은 RPC_C_IMP_LEVEL_IDENTIFY입니다. 클라이언트 응용 프로그램이 RPC_C_IMP_LEVEL_IDENTIFY를 사용하는 경우 CoImpersonateClient는 오류 코드를 반환하지 않습니다. 대신 공급자는 식별 목적으로만 클라이언트를 가장합니다. 따라서 공급자가 호출하는 대부분의 Windows 메서드는 액세스 거부 메시지를 반환합니다. 사용자가 부적절한 작업을 수행할 수 없으므로 실제로는 무해합니다. 그러나 공급자 개발 중에 클라이언트가 실제로 가장되었는지 여부를 파악하는 것이 유용할 수 있습니다.

코드를 올바르게 컴파일하려면 다음 참조 및 #include 문이 필요합니다.

#define _WIN32_DCOM
#include <iostream>
using namespace std;
#include <wbemidl.h>

다음 코드 예제에서는 공급자가 클라이언트 응용 프로그램을 성공적으로 가장했는지 여부를 확인하는 방법을 보여줍니다.

DWORD dwImp = 0;
HANDLE hThreadTok;
DWORD dwBytesReturned;
BOOL bRes;

// You must call this before trying to open a thread token!
CoImpersonateClient();

bRes = OpenThreadToken(
    GetCurrentThread(),
    TOKEN_QUERY,
    TRUE,
    &hThreadTok
);

if (bRes == FALSE)
{
    printf("Unable to read thread token (%d)\n", GetLastError());
    return 0;
}

bRes = GetTokenInformation(
    hThreadTok,
    TokenImpersonationLevel, 
    &dwImp,
    sizeof(DWORD),
    &dwBytesReturned
);

if (!bRes)
{
    printf("Unable to read impersonation level\n");
    CloseHandle(hThreadTok);
    return 0;
}

switch (dwImp)
{
case SecurityAnonymous:
    printf("SecurityAnonymous\n");
    break;

case SecurityIdentification:
    printf("SecurityIdentification\n");
    break;

case SecurityImpersonation:
    printf("SecurityImpersonation\n");
    break;

case SecurityDelegation:
    printf("SecurityDelegation\n");
    break;

default:
    printf("Error. Unable to determine impersonation level\n");
    break;
}

CloseHandle(hThreadTok);

WMI 공급자 개발

네임스페이스 보안 설명자 설정

공급자 보안