问题描述
我正在使用第三方 Windows 服务,该服务通过使用 CreateProcessAsUser() 运行脚本和可执行文件来处理一些自动化任务.由于 UAC 以及通过 API 处理 LUA 提升的方式,我在 Windows Server 2008 上遇到了问题.
I'm using a thrid party Windows service that handles some automation tasks by running scripts and executables using CreateProcessAsUser(). I'm running into problems on Windows Server 2008 due to UAC and way the LUA elevation is handled through the APIs.
该服务作为 LocalSystem 运行,并且没有启用与桌面交互".这些进程以管理员组中的用户身份运行,而不是管理员帐户(不受许多 UAC 限制).所有 UAC 默认设置到位.
The service runs as LocalSystem and does not have "Interact With Desktop" enabled. The processes are being run as users in the Administrators group, but not the Administrator account (which is exempted from many UAC restrictions). All the UAC default settings in place.
我可以将任意命令或 powershell 代码传递给服务,但我似乎无法摆脱"由服务启动的非高级、非交互式进程.
I can pass arbitrary commands or powershell code to the service, but I can't seem to 'break out' of the non-elevated, non-interactive process that gets kicked off by the service.
问题的关键似乎是启动提升进程的唯一(公共)API 选项是带有runas"动词的 ShellExecute(),但据我所知,不能从非交互式服务,否则您会收到此操作需要交互式窗口站"之类的错误.
The crux of the issue seems to be that the only (public) API option for starting an elevated process is ShellExecute() with the 'runas' verb, but as far as i can tell that can't be called from a non-interactive service or you get errors like "This operation requires an interactive window station".
这里提到了我发现的唯一解决方法:http://www.eggheadcafe.com/软件/aspnet/29620442/how-to-proper-use-sendinp.aspx
The only workaround I've found is mentioned here:http://www.eggheadcafe.com/software/aspnet/29620442/how-to-proper-use-sendinp.aspx
在Vista中,官方文档化的方式提升一个过程只是使用shell API ShellExecute(Ex)(不CreateProcess 或 CreateProcessAsUser).所以你的应用程序必须调用ShellExecute(Ex) 来启动一个助手提升到调用 SendInput.此外,由于会话 0隔离,一个服务只能使用CreateProcessAsUser 或CreateProcessWithLogonW(不能使用ShellExecute(Ex)) 来指定交互式桌面.
..我认为没有直接的方法从一个提升的进程产生窗口服务.我们只能先用CreateProcessAsUser 或CreateProcessWithLogonW 生成一个非提升过程到用户会话(交互式桌面).然后在非提升过程,它可以使用ShellExecute(Ex) 生成一个提升的实际任务的流程.
..I think there is no direct way to spawn an elevated process from a windows service. We can only first use CreateProcessAsUser or CreateProcessWithLogonW to spawn a non-elevated process into the user session(interactive desktop). Then in the non-elevated process, it may use ShellExecute(Ex) to spawn an elevated process for the real task.
要从 .net/powershell 代码执行此操作,看起来我必须执行一些精心设计的 P/Invoke 操作才能调用 CreateProcessAsUser 或 CreateProcessWithLogonW,因为 .Net System.Diagnostics.ProcessStartInfo 没有等效的 lpDesktop我可以设置为winsta0\default".而且我不清楚 LocalSystem 是否甚至有权调用 CreateProcessAsUser 或 CreateProcessWithLogonW.
To do this from .net/powershell code, it looks like I'd have to do some elaborate P/Invoke stuff to call CreateProcessAsUser or CreateProcessWithLogonW since the .Net System.Diagnostics.ProcessStartInfo doesn't have an equivalent of lpDesktop that I could set to "winsta0\default". And I'm not clear on if LocalSystem even has the rights to call CreateProcessAsUser or CreateProcessWithLogonW.
我也看了http://blogs.msdn.com/alejacma/archive/2007/12/20/how-to-call-createprocesswithlogonw-createprocessasuser-in-net.aspx和Process.Start 使用不同的凭据并开启 UAC
基于所有这些,我得出的结论是没有直接的方法可以做到这一点.我错过了什么吗?这似乎真的不应该那么难.感觉 UAC 从来没有被设计用来处理非交互式用例.
Based on all that, I'm reaching the conclusion that there's no straightforward way to do this. Am I missing something? This really doesn't seem like it should be so hard. It feels like UAC was just never designed to handle non-interactive use cases.
如果有任何 Microsoft 人员最终阅读了本文,我注意到 ShellExecute 内部处理提升的方式是调用应用程序信息服务 (AIS).为什么不能通过某些 Win32 或 .NET API 调用 AIS?http://msdn.microsoft.com/en-us/library/bb756945.aspx
And if any Microsoft people end up reading this, I noticed that the way ShellExecute internally handles elevation is by calling out to Application Information Service (AIS). Why isn't that same call to AIS available through some Win32 or .NET API?http://msdn.microsoft.com/en-us/library/bb756945.aspx
抱歉跑的有点长.感谢您的任何想法.
Sorry that ran a bit long. Thanks for any ideas.
推荐答案
打破会话零隔离的官方"方式是使用终端服务 API 和 CreateProcessAsUser()
的组合来启动用户会话中的一个进程.在我以前的工作中,我们就是这样做的,因为我们需要在安装下载的更新之前从服务向用户显示一个对话框 所以,我知道它至少可以在 WinXP、Win2K3、Vista 和 Win7 上工作,但是我不要指望Win 2K8 会有太大的不同.基本上,过程如下:
The "official" way to break session zero isolation is to use a combination of the terminal services API and CreateProcessAsUser()
to launch a process within a user's session. At my old job, we did just that, as we needed to display a dialog to the user from a service prior to installing a downloaded update So, I know it works, on WinXP, Win2K3, Vista, and Win7 at least, but I don't expect that Win 2K8 would be too different. Basically, the process goes as follows:
- 调用
WTSGetActiveConsoleSessionId()
以获取活动的控制台会话 ID(非常重要,因为交互式会话不总是会话 1,即使在客户端系统上也是如此).如果没有活动用户登录到交互式会话(即本地登录到物理机,而不是使用 RDP),此 API 还将返回 -1. - 将上一次 API 调用中的会话 ID 传递给
WTSQueryUserToken()
以获取代表用户登录控制台的开放令牌. - 调用
DuplicateTokenEx()
将模拟令牌(来自WTSQueryUserToken
)转换为主要令牌. - 调用
CreateEnvironmentBlock()
为进程创建一个新环境(可选,但如果不这样做,进程将没有环境). - 将第 3 步中的主要令牌与可执行文件的命令行一起传递到对
CreateProccessAsUser()
的调用中.如果您从第 4 步创建了环境块,则还必须(始终)传递CREATE_UNICODE_ENVIRONMENT
标志.这可能看起来很愚蠢,但如果您不这样做,API 就会严重失败(使用ERROR_INVALID_PARAMTER
). - 如果你创建了环境块,那么你需要调用
DestroyEnvironmentBlock
,否则会产生内存泄漏.该进程在启动时会获得一份单独的环境块副本,因此您只会破坏本地数据.
- Call
WTSGetActiveConsoleSessionId()
to get the active console session id (VERY important, as the interactive session is NOT always session 1, even on client systems). This API will also return a -1 if there is no active user logged into the interactive session (that is, logged in locally to the physical machine, as opposed to using RDP). - Pass the session id from the previous API call to
WTSQueryUserToken()
to get an open token that reprents the user logged into the console. - Call
DuplicateTokenEx()
to convert the impersonation token (fromWTSQueryUserToken
) into a primary token. - Call
CreateEnvironmentBlock()
to create a new environment for the process (optional, but if you don't, the process won't have one). - Pass the primary token from step #3 into a call to
CreateProccessAsUser()
, along with the command line for the executable. If you created an environment block from step #4, you must pass theCREATE_UNICODE_ENVIRONMENT
flag as well (always). This may seem silly, but the API fails horribly if you don't (withERROR_INVALID_PARAMTER
). - If you created an environment block, then you need to call
DestroyEnvironmentBlock
, otherwise you will generate a memory leak. The process is given a separate copy of the environment block when it launches, so you are only destroying local data.
瞧!Windows 执行了一些内部魔术,您会看到应用程序启动.但是,虽然这将从服务启动和交互过程,但我不确定它是否会绕过 UAC(但不要引用我的话).换句话说,除非注册表或内部清单说这样做,否则它可能不会作为提升的进程启动,即使这样,您仍然可能会收到 UAC 提示.如果您从第 3 步获得的令牌是受限令牌,您可以使用 AdjustTokenPrivileges()
来恢复提升的(完整)令牌,但也不要引用我的话.但是,如 MSDN 文档中所述,请注意无法在尚未拥有权限的令牌上添加"权限(例如,您无法使用 AdjustTokenPrivileges 将受限用户令牌转换为管理员
;底层用户必须是管理员才能开始).
And voila! Windows does some internal magic, and you see the application launch. However, although this will launch and interactive process from a service, I am not sure if it will bypass UAC (but don't quote me on that). In other words, it may not launch as an elevated process unless the registry or internal manifest says to do so, and even then, you will might still get a UAC prompt. If the token you get from step #3 is a restricted token, you may be able to use AdjustTokenPrivileges()
to restore the elevated (full) token, but don't quote me on that, either. However, as stated in the MSDN docs, please note that it is not possible to "add" privileges on a token that did not already have them (e.g. you can't turn a restricted user token into an administrator by using AdjustTokenPrivileges
; the underlying user would have to be an admin to start with).
从 Win2K 开始,这在技术上是可行的.然而,它真的只有从 WinXP 开始才可行,因为 Win2K 缺少 WTSGetActiveConsoleSessionId()
和 WTSQueryUserToken()
API(以及 WTSEnumerateProcesses()
Win2K Pro).您可以将 0 硬编码为会话 ID(因为在 Win2K 中总是如此),我想您可以通过枚举正在运行的进程并复制其令牌之一来获取用户令牌(它应该是具有交互式 SID 存在).无论如何,CreateProcessAsUser()
在传递交互式用户令牌时的行为方式相同,即使您没有从服务设置中选择与桌面交互".无论如何,它也比直接从服务启动更安全,因为该进程不会继承神圣的 LocalSystem
访问令牌.
It is technically possible to do all this from Win2K forward. However, it is really only feasible starting with WinXP, as Win2K lacks the WTSGetActiveConsoleSessionId()
and WTSQueryUserToken()
API's (along with WTSEnumerateProcesses()
for Win2K Pro). You can hard code 0 as the session id (since that is always the case in Win2K), and I suppose you might be able to get the user token by enumerating the running processes and duplicating one of their tokens (it should be one that has the interactive SID present). Regardless, the CreateProcessAsUser()
will behave the same way when passed an interactive user token, even if you don't select "Interact with the Desktop" from the service settings. It is also more secure than launching directly from the service anyway, as the process will not inherit the godly LocalSystem
access token.
现在,我不知道您的第三方应用程序在运行脚本/进程时是否会执行这些操作,但是如果您想从服务中执行此操作,就是这样(对于 Vista 或 Win7,这是克服会话 0 隔离的唯一方法).
Now, I don't know if your third party app does any of this when it runs the script/process, but if you want to do it from a service, that is how (and with Vista or Win7, it's the only way to overcome session 0 isolation).
这篇关于从非交互式服务 (win32/.net/powershell) 启动 UAC 提升进程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!