다음을 통해 공유


AppContainer 시작

이 문서에서는 관련 코드 예제를 포함하여 AppContainer 또는 권한 없는 AppContainer를 시작하는 데 필요한 단계를 설명합니다. AppContainers는 애플리케이션 프로세스의 격리 및 제어를 향상시키기 위해 Windows 8에 도입된 보안 기능입니다. 명시적으로 허용되지 않는 한 사용자 데이터뿐만 아니라 시스템 또는 서로의 리소스에 액세스하는 기능을 제한하는 애플리케이션에 대한 샌드박스 환경을 제공합니다. AppContainers는 SID(보안 식별자), 토큰 및 보안 설명자 DACL(임의 액세스 제어 목록)과 같은 기존 Windows 보안 메커니즘을 활용하여 이러한 제한을 적용합니다.

용어

다음 표에서는 이 문서에서 참조하는 용어와 개념을 정의합니다.

용어 Description
패키지 ID 패키지 ID는 패키지를 고유하게 식별하는 논리적 구문입니다. 자세한 내용은 Windows 앱의 패키지 ID 개요를 참조하세요.
SID(보안 식별자) SID는 보안 주체 또는 보안 그룹을 고유하게 식별하는 데 사용됩니다. 보안 주체는 운영 체제에서 인증할 수 있는 모든 엔터티를 나타낼 수 있습니다. 예를 들어 사용자 계정, 컴퓨터 계정 또는 사용자 또는 컴퓨터 계정의 보안 컨텍스트에서 실행되는 스레드 또는 프로세스가 있습니다. 자세한 내용은 보안 식별자를 참조하세요.
기능 SID 기능 SID는 기능에 대해 고유하고 변경할 수 없는 식별자 역할을 합니다. 기능은 애플리케이션에 리소스(예: 문서, 카메라 및 위치)에 대한 액세스 권한을 부여하는 잊을 수 없는 권한 토큰을 나타냅니다. 자세한 내용은 앱 기능 선언을 참조하세요.
DACL(임의 액세스 제어 목록) 개체에서 다양한 작업을 수행할 수 있는 사용자 및 그룹을 식별하는 목록입니다. 자세한 내용은 보안 설명자 구성 요소를 참조하세요.
LPAC(덜 권한 있는 AppContainers) 일반 AppContainer보다 격리된 AppContainer의 형식입니다. AppContainers에 액세스할 수 있는 리소스에 액세스하려면 명시적 기능 선언이 필요합니다.

AppContainer 개요

애플리케이션이 AppContainer로 실행되는 경우 해당 액세스 토큰에는 고유한 애플리케이션 패키지 ID(패키지 SID) 및 하나 이상의 기능 SID가 포함됩니다. AppContainers의 경우 AppContainers가 가능한 최소 권한으로 실행되고 필요한 경우 잠재적으로 중요한 리소스에 대한 액세스 권한만 부여할 수 있도록 하는 데 기능이 사용됩니다. 예를 들어 네트워크 기능이 없으면 AppContainer는 카메라에 액세스할 수 없는 웹캠 기능이 없으면 네트워크에 액세스할 수 없습니다. AppContainer SID(패키지 및 기능 SID)는 기존 사용자 및 그룹 SID와는 별개이며, 토큰의 두 부분이 모두 개체의 DACL(임의 액세스 제어 목록)을 통해 보호된 리소스에 대한 액세스 권한을 부여하는 데 필요합니다. 이 이중 보안 주체 모델은 중요한 리소스에 대한 액세스를 엄격하게 제어하고 다양한 애플리케이션에 대해 독립적으로 관리할 수 있도록 합니다. 또한 AppContainers에 지정된 리소스에 대한 액세스 권한을 명시적으로 부여해야 합니다. 또한 허용되는 액세스는 사용자/그룹 SID 및 AppContainer SID에서 부여한 액세스의 교집합이므로 사용자가 모든 권한을 가지고 있지만 AppContainer에 읽기 권한만 있는 경우 AppContainer에 읽기 권한만 부여할 수 있습니다. 마찬가지로 사용자가 읽기 및 실행 액세스 권한이 있지만 AppContainer에 모든 액세스 권한이 있는 경우 AppContainer는 읽기 및 실행 액세스 권한만 부여할 수 있습니다.

AppContainers는 IL(낮은 무결성 수준)으로 실행되어 시스템에서 더 높은 무결성 개체와 상호 작용하는 기능을 추가로 제한합니다. 그러나 리소스에 중간 IL 이하의 필수 레이블이 있고 DACL이 AppContainer에 대한 액세스 권한을 부여하는 경우(패키지 SID 또는 기능 SID를 통해) AppContainer는 DACL의 두 보안 주체에 부여된 액세스 권한에 따라 읽거나 쓰거나 실행할 수 있습니다. 이 방법은 유연성과 보안을 모두 제공하므로 신뢰할 수 없거나 손상된 애플리케이션에서 잠재적으로 유해한 작업을 제한하면서 필요한 리소스에 액세스할 수 있습니다.

AppContainers는 디바이스, 파일/디렉터리, 레지스트리 키, 네트워크 및 자격 증명뿐만 아니라 다른 애플리케이션에 속하는 프로세스 및 창에 액세스하지 못하도록 격리됩니다. 따라서 신뢰할 수 없는 데이터를 안전하게 구문 분석하는 등 잠재적인 위험한 작업을 샌드박싱하기 위한 메커니즘을 제공합니다.

일반 AppContainers에는 특정 시스템 파일/디렉터리, 일반적인 레지스트리 키 및 COM 개체에 대한 액세스 권한이 부여되지만, LPAC에는 일반 AppContainers가 액세스할 수 있는 리소스에 액세스하기 위한 특정 기능이 필요합니다. LPAC(덜 권한 있는 AppContainers)는 일반 AppContainers보다 훨씬 더 격리되어 있으며, 일반 AppContainers가 레지스트리, 파일 등과 같은 액세스 권한이 이미 있는 리소스에 액세스하려면 추가 기능이 필요합니다. 예를 들어 LPAC는 registryRead 기능이 있고 lpacCom 기능이 없는 한 COM을 사용할 수 없는 한 레지스트리에서 키를 열 수 없습니다.

AppContainer 시작

앞에서 설명한 것처럼 AppContainers에는 고유한 패키지 SID가 있으므로 자체 리소스가 다른 애플리케이션으로부터 보호됩니다. 패키지 SID는 지정된 AppContainer의 문자열 이름(모니커)에서 파생됩니다. AppX 매니페스트를 통해 생성된 AppContainers의 경우 PFN(패키지 패밀리 이름)이지만 AppContainer 프로세스 자체를 시작하는 애플리케이션의 경우 애플리케이션은 AppContainer에 제공하려는 이름(모니커)을 결정해야 합니다.

AppContainer를 시작하는 데는 몇 가지 단계가 있습니다. AppContainer가 AppContainer를 만들어야 한다는 것을 Windows에 알리기 위해 AppContainer에서 파일, 프로세스 및 스레드 특성을 생성/읽기/쓸 수 있는 위치가 있도록 AppContainer에 대해 프로필을 만들어야 합니다.

기능 구성

AppContainer(또는 LPAC)는 네트워크, 위치 또는 LPAC의 경우 레지스트리 또는 COM 개체와 같은 다른 리소스에 액세스하는 기능이 필요할 수 있습니다. 그룹 SID는 서비스에만 사용되고 API는 단일 기능만 반환하므로 아래 예제 코드에 표시된 GetCapabilitySidFromName과 같은 도우미 함수에서 이 API를 래핑하는 것이 더 낫지만, DeriveCapabilitySidsFromName API를 통해 기능을 생성할 수 있습니다.

BOOL GetCapabilitySidFromName( 
    PCWSTR CapabilityName, 
    PSID* CapabilitySid) 
{ 
    PSID* CapabilitySids; 
    DWORD CapabilitySidCount; 
    PSID* GroupSids; 
    DWORD GroupSidCount; 

    *CapabilitySid = NULL; 

    if (DeriveCapabilitySidsFromName(CapabilityName, &GroupSids, &GroupSidCount, & CapabilitySids, &CapabilitySidCount)) 
    { 
        LocalFree(GroupSids[0]); 
        LocalFree(GroupSids); 

        *CapabilitySid = CapabilitySids[0]; 
        LocalFree(CapabilitySids); 
        return TRUE; 

    }

    return FALSE; 
} 

다음 예제 코드에서는 이전 예제에 정의된 GetCapabilitySidFromName 도우미 함수를 사용하여 internetClient위치 기능을 생성하는 방법을 보여 줍니다.

BOOL BuildAppContainerCapabilities( 
    PSID_AND_ATTRIBUTES* Capabilities, 
    DWORD* NumberOfCapabilities 
) 
{
    DWORD CapabilityCount; 
    PSID CapabilitySids[2]; 
    PSID_AND_ATTRIBUTES LocalCapabilities; 

    *Capabilities = NULL; 
    *NumberOfCapabilities = 0; 

    CapabilityCount = 0; 

    if (GetCapabilitySidFromName(L"internetClient", &CapabilitySids[CapabilityCount++]) == FALSE) 
    { 
        return FALSE; 
    } 

    if (GetCapabilitySidFromName(L"location", &CapabilitySids[CapabilityCount++]) == FALSE) 
    { 
        for (DWORD i = 0; i < CapabilityCount; ++i) 
        { 
            LocalFree(CapabilitySids[i]); 
        } 

        return FALSE; 
    } 

    LocalCapabilities =  
        (PSID_AND_ATTRIBUTES)HeapAlloc(GetProcessHeap(), 
        0, 
        CapabilityCount * sizeof(SID_AND_ATTRIBUTES)); 

    if (LocalCapabilities != NULL) 
    { 
        for (DWORD i = 0; i < CapabilityCount; ++i) 
        { 
            LocalCapabilities[i].Sid = CapabilitySids[i]; 
            LocalCapabilities[i].Attributes = SE_GROUP_ENABLED; 
        } 
    }
    else 
    { 
        for (DWORD i = 0; i < CapabilityCount; ++i) 
        {
            LocalFree(CapabilitySids[i]); 
        } 

        return FALSE; 
    } 

    *Capabilities = LocalCapabilities; 
    *NumberOfCapabilities = CapabilityCount; 

    return TRUE; 
} 

프로필 만들기

LocalAPPDATA 환경 변수를 통해 또는 GetAppContainerFolderPath를 호출하여 AppContainer에 액세스할 수 있는 프로필을 사용하여 CreateAppContainerProfile을 호출하여 AppContainer를 만듭니다. 새 AppContainer의 경우 AppContainer에 대한 패키지 SID도 반환합니다. 그러나 기존 AppContainer의 경우 DeriveAppContainerSidFromAppContainerName API를 사용하여 모니커에서 패키지 SID를 파생해야 합니다. 또한 TMPTEMP 환경 변수는 프로필 위치 아래 AppContainer에 액세스할 수 있는 디렉터리로 다시 경로가 지정됩니다.

LOCALAPPDATA=C:\Users\TestUser\AppData\Local\Packages\TestAppContainer\AC

TEMP=C:\Users\TestUser\AppData\Local\Packages\TestAppContainer\AC\Temp

TMP=C:\Users\TestUser\AppData\Local\Packages\TestAppContainer\AC\Temp

다음 예제 코드에서는 CreateAppContainerProfile 함수를 사용하여 AppContainer 프로필을 만들고 새 또는 기존 AppContainer에 대한 SID를 검색하는 방법을 보여 줍니다.

HRESULT
CreateProfileForAppContainer(
    PCWSTR AppContainerName,
    PSID_AND_ATTRIBUTES Capabilities,
    ULONG NumberOfCapabilities,
    PCWSTR DisplayName,
    PCWSTR Description,
    PSID* AppContainerSid
    )
{
    HRESULT hr;
    PSID LocalAppContainerSid = NULL;

    *AppContainerSid = NULL;

    hr = CreateAppContainerProfile(AppContainerName,
                                   DisplayName,
                                   Description,
                                   Capabilities,
                                   NumberOfCapabilities,
                                   &LocalAppContainerSid);

    if (FAILED(hr)) {
        if (hr == HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)) {

            //
            // Obtain the AppContainer SID based on the AppContainer name.
            //

            hr = AppContainerDeriveSidFromMoniker(AppContainerName,
                                                  &LocalAppContainerSid);
            
            if (FAILED(hr)) {   
                return hr;
            }

        } else {
            return hr;
        }        
    } 

    //
    // Since this is successful, set the output AppContainer SID accordingly.
    //
    
    *AppContainerSid = LocalAppContainerSid;

    return S_OK;
}

AppContainer(또는 LPAC) 시작

AppContainer 또는 LPAC 프로세스를 시작하려면 시작 정보 구조인 STARTUPINFOEX에 특정 필드를 포함해야 합니다. 특히 개체 네임스페이스 및 토큰을 포함하는 AppContainer에 대한 환경을 만들도록 CreateProcess에 지시하는 추가 정보를 허용하기 때문에 lpAttributeList 필드가 필요합니다. lpAttributeList 필드는 LPPROC_THREAD_ATTRIBUTE_LIST 형식이며 다음과 같이 설정됩니다.

다음 예제에서는 일반 앱 컨테이너를 시작하는 방법을 보여줍니다.

STARTUPINFOEX si = {0}; 
LPPROC_THREAD_ATTRIBUTE_LIST AttributeList = NULL; 
SECURITY_CAPABILITIES SecurityCapabilities; 
DWORD AttributeCount = 1; 
SIZE_T AttributesLength = 0; 

if (!InitializeProcThreadAttributeList(NULL, AttributeCount, 0, &AttributesLength)) 
{
    if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)  
    { 
        return GetLastError(); 
    }
} 

AttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 
    0, 
    AttributesLength); 

if (AttributeList == NULL) 
{ 
    return ERROR_OUTOFMEMORY; 
} 

if (!InitializeProcThreadAttributeList(AttributeList, AttributeCount, 0, &AttributesLength)) 
{ 
    if (GetLastError() != ERROR_SUCCESS) 
    {
        return GetLastError(); 
    }
} 

SecurityCapabilities.CapabilityCount = NumberOfCapabilities; 
SecurityCapabilities.Capabilities = Capabilities; 
SecurityCapabilities.AppContainerSid = PackageSid; 
SecurityCapabilities.Reserved = 0; 

if (!UpdateProcThreadAttribute(AttributeList, 
        0,
        PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES, 
        &SecurityCapabilities, 
        sizeof(SecurityCapabilities), 
        NULL, 
        NULL 
        )) 
{ 
    return GetLastError(); 
} 

si.StartupInfo.cb = sizeof(si); 
si.lpAttributeList = AttributeList; 

다음 예제에서는 추가 프로세스/스레드 특성이 필요한 덜 권한 있는 AppContainer(LPAC)를 시작하는 방법을 보여 줍니다.

STARTUPINFOEX si = {0}; 
LPPROC_THREAD_ATTRIBUTE_LIST AttributeList = NULL; 
SECURITY_CAPABILITIES SecurityCapabilities; 
DWORD AttributeCount = 2; 
SIZE_T AttributesLength = 0; 
DWORD AllApplicationPackagesPolicy; 

if (!InitializeProcThreadAttributeList(NULL, AttributeCount, 0, &AttributesLength)) 
{ 
    if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) 
    { 
        return GetLastError(); 
    } 
} 

AttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 
    0, 
    AttributesLength); 

if (AttributeList == NULL) 
{ 
    return ERROR_OUTOFMEMORY; 
} 

if (!InitializeProcThreadAttributeList(AttributeList, AttributeCount, 0, &AttributesLength)) 
{ 
    if (GetLastError() != ERROR_SUCCESS) 
    { 
        return GetLastError(); 
    } 
} 

SecurityCapabilities.CapabilityCount = NumberOfCapabilities; 
SecurityCapabilities.Capabilities = Capabilities; 
SecurityCapabilities.AppContainerSid = PackageSid; 
SecurityCapabilities.Reserved = 0; 

if (!UpdateProcThreadAttribute(AttributeList, 
    0, 
    PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES, 
    &SecurityCapabilities, 
    sizeof(SecurityCapabilities), 
    NULL, 
    NULL 
    )) 
{
    return GetLastError(); 
} 

AllApplicationPackagesPolicy = PROCESS_CREATION_ALL_APPLICATION_PACKAGES_OPT_OUT; 

if (!UpdateProcThreadAttribute(AttributeList, 
    0, 
    PROC_THREAD_ATTRIBUTE_ALL_APPLICATION_PACKAGES_POLICY, 
    &AllApplicationPackagesPolicy, 
    sizeof(AllApplicationPackagesPolicy), 
    NULL, 
    NULL 
    )) 
{ 
    return GetLastError(); 
} 

si.StartupInfo.cb = sizeof(si); 
si.lpAttributeList = AttributeList; 

AppContainer/LPAC 프로세스 만들기

마지막 단계는 이전 단계에서 생성된 프로세스/스레드 특성을 포함하는 시작 정보를 사용하여 프로세스를 시작하는 것입니다. 여기에는 패키지 SID, 기능(있는 경우)뿐만 아니라 모든 애플리케이션 패키지를 옵트아웃하여 지정하는 LPAC가 포함됩니다.

if (!CreateProcess(NULL, 
    <path to executable>, 
    NULL, 
    NULL, 
    FALSE, 
    EXTENDED_STARTUPINFO_PRESENT, 
    NULL, 
    NULL, 
    (LPSTARTUPINFOW)&si, 
    &pi)) 
{ 
    return GetLastError(); 
}

보안 식별자

보안 설명자 구성 요소