服务是整合在Microsoft Windows操作系统中 的结构。服务与用户的应用程序不同,因为你可以对他们进行配置,不需要一个激活的用户 登录,就可以使这些服务在系统启动的时候运行,直到系统关闭。Windows中的服务,负责所 有种类的后台活动,但不包括从远程过程调用(RPC)服务到网络位置服务的用户。
一些服务可能会试图显示一些用户界面对话框,或者与用户的应用程序进行通信。那么这 些服务将会遇到与Windows 7 的兼容性问题。可能你有一个服务,试图显示一个对话框,但 是,可能仅仅是在任务栏中出现一个闪烁的图标。无独有偶,你的服务可能会多多少少遇到 下面的一些现象。这些服务:
? 试图通过窗体消息,与用户的应用 程序进行通信,但是窗体消息并没有被目标所接收到
? 在任务栏中,显示一个 闪烁的图标,说明此服务希望与桌面信息进行交互
这些是Windows 7的服务的Session 0隔离机制的一些症状。它们可以大致分为两大类:
? 服务显示用户界面失败, 或者仅仅显示了一个简化的用户界面
当一个服务试图去展示任何有关用户界面的元素 (即使这个服务被允许与桌面信息进行交互),一个叫做Interactive services dialog detection的简单对话框也会弹出,用于提示用户。用户可以进入Session 0的安全桌面中查 看服务的用户界面,但是,工作流中的干扰,使得这个变成了一个严重的应用程序兼容性问 题。
当一个对象由服务创建出 来,并且允许标准应用程序访问(以标准用户权限运行),那么这个对象将不能在全局命名 空间中找到(它将被Session 0 中私有)。此外,安全变更,也将保证即使对象是可见的, 但它也是不能被访问的。这些将有可能影响其他的与你的服务进行交互的进程(比如普通用 户的应用程序)。
真正的问题是Windows 7 服务的Session 0 隔离机制
在 Windows XP, Windows Server 2003或者更早期的Windows操作系统中,所有的服务和应用程 序都是运行在与第一个登录到控制台的用户得Session中。这个Session叫做Session 0。在 Session 0 中一起运行服务和用户应用程序,由于服务是以高权限运行的,所以会造成一些 安全风险。这些因素使得一些恶意代理利用这点,来寻找提升他们自身权限的结构。
在Windows Vista中,服务在一个叫做Session 0 的特殊Session中承载。由于应用程序运 行在用户登录到系统后所创建的Session 0 之后的Session中,所以应用程序和服务也就隔离 开来:第一个登录的用户在Session 1中,第二个在Session 2中,以此类推。事实上运行在 不同的Session中,如果没有特别将其放入全局命名空间(并且设置了相应的访问控制配置) ,是不能互相传递窗体消息,共享UI元素或者共享kernel对象。下面的图例中,将进行图解 :
? 如果一个服务,需要向用户传递一条消息,那么可以使用WTSSendMessage方 法。这个方法和MessageBox基本相同。它将可以给不需要复杂的UI界面的服务,一个简单而 有效的解决方案,同时,由于所显示的消息对话框不能用于控制底层的服务,所以,这个解 决方案也是安全的。
? 如果需要使用一个复杂的UI界面,那么可以使用 CreateProcessAsUser方法,在提出请求的用户桌面中创建一个进程。
? 如果这 两种交互方式都需要,那么可以使用诸如Windows Communication Foundation (WCF),.NET 远程处理,命名管道或者其他的进程间通信(IPC)结构(除窗体消息之外)来跨Session通 信。
? 安全通信和其他共享对象(比如,命名管道,文件地图),可以使用任 意访问控制列表(DACL)来严格控制用户的权限设置。使用系统访问控制列表(SACL),来 确保中低权限的进程依然可以访问由系统或高权限服务所创建的结构。
? 确保 跨Session访问的kernel对象是以Global\字符串为首字母进行命名,这意味着他们是属于全 局Session命名空间中的。
目前,我们已经遇到了所有的 Windows服务的Session 0 隔离机制的情况,解释了什么是服务隔离,它是如何影响你的服务 和应用程序的,并且提出了一些解决方案。下面的一些测试和一些操作,可以帮助你找到问 题,并且解决它。
a.下载或了解更多关于进程浏览器,请在Microsoft TechNet网站参看Process Explorer Web site 。
b.选择 Show processes from all users.
d.请注意服务运行在哪个Session中(通常在Session 0),并且它的全部级别。
下面是两个进程的例子-其中一个以中级权限运行(在Session 1),另外一个则以系统权 限运行(在Session 1):
如果你的服务是以高权限运行在Session 0中,那么它将不能直接显示UI。不过即使这样 ,当你与服务进行共享kernel对象或者文件的时候,你可能还会遇到一些问题。
b.选择 Show processes from all users.
4.如果服务包含了你已知 的与用户应用程序共享的对象,请在下面的Handles窗口中检查他们的句柄(使用CTRL+H查看 ,或者从View菜单中进入)。
b.切换到Security标签页,查看当前用户和组是否允许使用当前句柄来访问对象。
下面的图中,展示了一个不管是否是在Session 0 中运行的系统服务中,所有人都可 以访问的共享对象("同步"权限):
下面的 图中,展示了一个只允许管理员和系统组才能访问的共享对象:
5.如果服务需要创建一个用户应用程序可以访问的文件,请依照以下步骤:
b.运行icacls工具查看文件或者目录的集合权限和DACL信息,并且修改它们。
( 请在Microsoft TechNet网站查看关于icacls的文档,以获取更多信息。)
c.运行 icacls MyFile来显示叫MyFile的文件的访问控制列表。
d.运行icacls MyFile /setintegritylevel Medium来将MyFile的集合权限级别修改为中级(这将使它可以访问大部 分用户应用程序。)
进程浏览器 –Windows进程的监视工 具,可以显示进程的集合级别和对象的安全信息。
icacls –Windows实用工具中的一个,用于更改文件系统对象的ACL和整合级别。
使用WTSSendMessage从服务端向当前活动用户的桌面发送消息
当需要显示一个相对复杂的UI界面时,如何在当前活动用户的桌面创建一个 进程
创建一个属于全局命名空间的事件,并且包含安全配置,以便于它可以 被当前活动用户所访问。
使用WTSSendMessage 从服务端向当前活动用户的桌面发送 消息:
void ShowMessage(LPWSTR lpszMessage, LPWSTR lpszTitle)
{
DWORD dwSession = WTSGetActiveConsoleSessionId();
DWORD dwResponse = 0;
WTSSendMessage(WTS_CURRENT_SERVER_HANDLE
,dwSession
, lpszTitle
, static_cast<DWORD>((wcslen(lpszTitle) + 1) * sizeof(wchar_t))
, lpszMessage
,static_cast<DWORD>((wcslen(lpszMessage) + 1) * sizeof(wchar_t))
, 0, 0, &dwResponse, FALSE);
}
当需要显示一个相对复杂的UI 界面时,如何在当前活动用户的桌面创建一个进程:
BOOL bSuccess = FALSE;
STARTUPINFO si = {0};
PROCESS_INFORMATION pi = {0};
si.cb = sizeof(si);
DWORD dwSessionID = WTSGetActiveConsoleSessionId();
HANDLE hToken = NULL;
if (WTSQueryUserToken(dwSessionID, &hToken) == FALSE)
{
goto Cleanup;
}
HANDLE hDuplicatedToken = NULL;
if (DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hDuplicatedToken) == FALSE)
{
goto Cleanup;
}
LPVOID lpEnvironment = NULL;
if (CreateEnvironmentBlock(&lpEnvironment, hDuplicatedToken, FALSE) == FALSE)
{
goto Cleanup;
}
WCHAR lpszClientPath[MAX_PATH];
if (GetModuleFileName(NULL, lpszClientPath, MAX_PATH) == 0)
{
goto Cleanup;
}
PathRemoveFileSpec(lpszClientPath);
wcscat_s(lpszClientPath, sizeof(lpszClientPath)/sizeof(WCHAR), L"\\TimeServiceClient.exe");
if (CreateProcessAsUser(hDuplicatedToken, lpszClientPath, NULL, NULL, NULL, FALSE,
NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT,
{
goto Cleanup;
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
bSuccess = TRUE;
Cleanup:
if (!bSuccess)
{
ShowMessage(L"An error occurred while creating fancy client UI", L"Error");
}
if (hToken != NULL)
CloseHandle(hToken);
if (hDuplicatedToken != NULL)
CloseHandle(hDuplicatedToken);
if (lpEnvironment != NULL)
DestroyEnvironmentBlock (lpEnvironment);
创建一个属于全局命名空间的事件,并且包含安全配置,以便于它可以被当前活动用户所 访问(从DACL 和SACL ):
DWORD dwSessionID = WTSGetActiveConsoleSessionId();
HANDLE hToken = NULL;
if (WTSQueryUserToken(dwSessionID, &hToken) == FALSE)
{
goto Cleanup;
}
DWORD dwLength;
TOKEN_USER* account = NULL;
if (GetTokenInformation(hToken, TokenUser, NULL, 0, &dwLength) == FALSE &&
GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
goto Cleanup;
}
account = (TOKEN_USER*)new BYTE[dwLength];
if (GetTokenInformation(hToken, TokenUser, (LPVOID)account, dwLength, &dwLength) == FALSE)
{
goto Cleanup;
}
LPWSTR lpszSid = NULL;
if (ConvertSidToStringSid(account->User.Sid, &lpszSid) == FALSE)
{
goto Cleanup;
}
WCHAR sddl[1000];
wsprintf(sddl, L"O:SYG:BAD:(A;;GA;;;SY)(A;;GA;;;%s)S:(ML;;NW;;;ME)", lpszSid);
PSECURITY_DESCRIPTOR sd = NULL;
if (ConvertStringSecurityDescriptorToSecurityDescriptor(sddl, SDDL_REVISION_1, &sd, NULL) == FALSE)
{
goto Cleanup;
}
SECURITY_ATTRIBUTES sa;
sa.bInheritHandle = FALSE;
sa.lpSecurityDescriptor = sd;
sa.nLength = sizeof(sa);
g_hAlertEvent = CreateEvent(&sa, FALSE, FALSE, L"Global\\AlertServiceEvent");
if (g_hAlertEvent == NULL)
{
goto Cleanup;
}
while (!g_Stop)
{
Sleep(5000);
SetEvent(g_hAlertEvent);
}
Cleanup:
if (hToken != NULL) CloseHandle(hToken);
if (account != NULL) delete[] account;
if (lpszSid != NULL) LocalFree(lpszSid);
if (sd != NULL) LocalFree(sd);
if (g_hAlertEvent == NULL) CloseHandle(g_hAlertEvent);