本文介绍启动 AppContainer 或 Less-Privileged AppContainer(包括相关代码示例)所需的步骤。 AppContainers 是 Windows 8 中引入的一项安全功能,用于增强应用程序进程的隔离和控制。 它们为应用程序提供沙盒环境,限制其访问系统或彼此资源的能力,以及用户数据,除非明确允许。 AppContainers 利用现有的 Windows 安全机制(例如安全标识符(SID)、令牌和安全描述符自由访问控制列表(DACL)来强制实施这些限制。
术语
下表定义了本文中引用的术语和概念。
| 术语 | Description |
|---|---|
| 包标识 | 包标识是一个逻辑构造,唯一标识包。 有关详细信息,请参阅 Windows 应用中的包标识概述。 |
| 安全标识符 (SID) | SID 用于唯一标识安全主体或安全组。 安全主体可以表示作系统可以进行身份验证的任何实体。 示例包括用户帐户、计算机帐户或在用户或计算机帐户的安全上下文中运行的线程或进程。 有关详细信息,请参阅 安全标识符 |
| 能力 SID | 功能 SID 充当功能的唯一不可变标识符。 功能表示无法预测的颁发机构令牌,该令牌授予应用程序对资源(例如文档、相机和位置)的访问权限。 有关详细信息,请参阅 应用功能声明 |
| 自由访问控制列表 (DACL) | 一个列表,用于标识可对对象执行各种作的用户和组。 有关详细信息,请参阅 安全描述符组件。 |
| 权限较低的 AppContainers (LPAC) | 比常规 AppContainer 更隔离的 AppContainer 类型。 它需要显式功能声明才能访问 AppContainers 可访问的资源。 |
AppContainer 概述
当应用程序作为 AppContainer 运行时,其访问令牌包括唯一的应用程序包标识(包 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 可以访问的资源。 特权较低的 AppContainers(LPAC)比常规 AppContainer 更隔离,并且需要进一步的功能来访问常规 AppContainers 已经有权访问的资源,例如注册表、文件和其他资源。 例如,LPAC 无法打开注册表中的任何键,除非它具有 registryRead 功能,并且不能使用 COM,除非它具有 lpacCom 功能。
启动 AppContainer
如前所述,AppContainers 具有唯一的 Package SID,可确保自己的资源受到其他应用程序的保护。 包 SID 派生自给定 AppContainer 的字符串名称(名字对象)。 对于通过 AppX 清单生成的 AppContainers,这是包系列名称(PFN),但在应用程序启动 AppContainer 进程本身时,应用程序需要确定它希望为 AppContainer 提供的名称(名字对象)。
启动 AppContainer 涉及几个步骤。 需要确定授予所需的功能,需要为 AppContainer 创建配置文件,以便 AppContainer 可以创建/读取/写入文件的位置,需要包含进程和线程属性,以通知 Windows 它需要创建 AppContainer。
构造功能
AppContainer(或 LPAC)可能需要功能来访问不同的资源,例如网络、位置或 LPAC 甚至注册表或 COM 对象。 构造功能可以通过 DeriveCapabilitySidsFromName API 来实现,尽管最好将此 API 包装在帮助程序函数中,如以下示例代码中显示的 GetCapabilitySidFromName ,因为组 SID 仅用于服务,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;
}
创建配置文件
通过调用 CreateAppContainerProfile 创建 AppContainer,可通过 LOCALAPPDATA 环境变量或调用 GetAppContainerFolderPath 访问 AppContainer 的配置文件。 对于新的 AppContainer,这也返回 AppContainer 的包 SID。 但是,对于现有的 AppContainer,必须使用 DeriveAppContainerSidFromAppContainerName API 从名字对象派生包 SID。 TMP 和 TEMP 环境变量也将重新路由到配置文件位置下的 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 或现有 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 中包含某些字段。 具体而言,需要 lpAttributeList 字段,因为这样就可以指示 CreateProcess 为 AppContainer 创建环境(包括对象命名空间和令牌)的其他信息。 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();
}