从非交互式服务启动 UAC 提升进程 (win32/.net/powershell)

发布于 2024-08-25 07:18:26 字数 2201 浏览 10 评论 0原文

我正在使用第三方 Windows 服务,该服务通过使用 CreateProcessAsUser() 运行脚本和可执行文件来处理一些自动化任务。由于 UAC 以及通过 API 处理 LUA 提升的方式,我在 Windows Server 2008 上遇到了问题。

该服务作为本地系统运行,并且未启用“与桌面交互”。这些进程以管理员组中的用户身份运行,但不是管理员帐户(不受许多 UAC 限制)。所有 UAC 默认设置均已到位。

我可以将任意命令或 powershell 代码传递给服务,但我似乎无法“突破”由服务启动的非提升、非交互式进程。

问题的关键似乎是启动提升进程的唯一(公共)API 选项是带有“runas”动词的 ShellExecute(),但据我所知,不能从非交互式调用服务,或者您收到诸如“此操作需要交互式窗口站”之类的错误。

我发现的唯一解决方法在这里提到: http://www.eggheadcafe.com/软件/aspnet/29620442/how-to-proper-use-sendinp.aspx

在 Vista 中,官方记录的方式 提升进程仅使用 shell API ShellExecute(Ex)(不是 CreateProcess 或 CreateProcessAsUser)。 所以你的应用程序必须调用 ShellExecute(Ex) 启动助手 提升为调用 SendInput。 此外,由于会话 0 隔离,一个服务只能使用 创建进程作为用户或 CreateProcessWithLogonW(不能使用 ShellExecute(Ex)) 来指定 交互式桌面。

..我认为没有直接的方法 产生一个提升的进程 窗口服务。我们只能先使用 创建进程作为用户或 CreateProcessWithLogonW 生成一个 非提升进程进入用户 会话(交互式桌面)。然后在 非提升过程,它可以使用 ShellExecute(Ex) 生成提升的 实际任务的流程。

要从 .net/powershell 代码执行此操作,看起来我必须执行一些复杂的 P/Invoke 操作来调用 CreateProcessAsUser 或 CreateProcessWithLogonW,因为 .Net System.Diagnostics.ProcessStartInfo 没有我可以使用的 lpDesktop 的等效项设置为“winsta0\default”。我不清楚 LocalSystem 是否有权调用 CreateProcessAsUser 或 CreateProcessWithLogonW。

我也看了 http: //blogs.msdn.com/alejacma/archive/2007/12/20/how-to-call-createprocesswithlogonw-createprocessasuser-in-net.aspxProcess.Start with different credential with UAC on

基于所有我得出的结论是,没有直接的方法可以做到这一点。我错过了什么吗?这看起来确实不应该那么难。感觉 UAC 从来就不是为处理非交互式用例而设计的。

如果任何 Microsoft 人员最终阅读了本文,我会注意到 ShellExecute 内部处理提升的方式是调用应用程序信息服务 (AIS)。为什么不能通过某些 Win32 或 .NET API 对 AIS 进行相同的调用? http://msdn.microsoft.com/en-us/library/bb756945。 aspx

抱歉,跑得有点长。感谢您的任何想法。

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.

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.

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.

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".

The only workaround I've found is mentioned here:
http://www.eggheadcafe.com/software/aspnet/29620442/how-to-proper-use-sendinp.aspx

In Vista, the official documented way
to elevate a process is only using the
shell API ShellExecute(Ex)(not
CreateProcess or CreateProcessAsUser).
So your application must call
ShellExecute(Ex) to launch a helper
elevated to call SendInput.
Furthermore, due to Session 0
isolation, a service can only use
CreateProcessAsUser or
CreateProcessWithLogonW(can not use
ShellExecute(Ex)) to specify the
interactive desktop.

..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.

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.

I also looked at
http://blogs.msdn.com/alejacma/archive/2007/12/20/how-to-call-createprocesswithlogonw-createprocessasuser-in-net.aspx
and
Process.Start with different credentials with UAC on

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.

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

仅此而已 2024-09-01 07:18:26

打破会话零隔离的“官方”方法是结合使用终端服务 API 和 CreateProcessAsUser() 在用户会话中启动进程。在我以前的工作中,我们就是这样做的,因为我们需要在安装下载的更新之前从服务向用户显示一个对话框所以,我知道它至少在 WinXP、Win2K3、Vista 和 Win7 上有效,但我不要指望Win 2K8会有太大的不同。基本上,该过程如下:

  1. 调用 WTSGetActiveConsoleSessionId() 获取活动控制台会话 ID(非常重要,因为交互式会话始终是会话 1,即使在客户端上也是如此)系统)。如果没有活动用户登录到交互式会话(即本地登录到物理机,而不是使用 RDP),此 API 也会返回 -1
  2. 将先前 API 调用中的会话 ID 传递给 WTSQueryUserToken() 以获取表示登录到控制台的用户的开放令牌。
  3. 调用 DuplicateTokenEx() 将模拟令牌(来自 WTSQueryUserToken)转换为主令牌。
  4. 调用 CreateEnvironmentBlock() 为进程创建一个新环境(可选,但如果不这样做,进程将没有环境)。
  5. 将步骤 #3 中的主令牌以及可执行文件的命令行传递到对 CreateProccessAsUser() 的调用中。如果您从步骤 #4 创建了环境块,则还必须传递 CREATE_UNICODE_ENVIRONMENT 标志(始终)。这可能看起来很愚蠢,但如果您不这样做,API 就会严重失败(出现 ERROR_INVALID_PARAMTER)。
  6. 如果你创建了一个环境块,那么你需要调用DestroyEnvironmentBlock,否则你将产生内存泄漏。该进程在启动时会获得环境块的单独副本,因此您只会破坏本地数据。

瞧! Windows 执行了一些内部魔法,您会看到应用程序启动。然而,虽然这将从服务启动和交互进程,但我不确定它是否会绕过 UAC(但不要引用我的话)。换句话说,除非注册表或内部清单要求这样做,否则它可能不会作为提升的进程启动,即使如此,您仍然可能会收到 UAC 提示。如果您从步骤 #3 获得的令牌是受限制的令牌,您可以使用 AdjustTokenPrivileges() 来恢复提升的(完整)令牌,但也不要引用我的话。但是,如 MSDN 文档中所述,请注意,无法在尚未拥有权限的令牌上“添加”权限(例如,您无法使用 AdjustTokenPrivileges 将受限用户令牌转变为管理员;底层用户必须是管理员)。

从 Win2K 开始,这一切在技术上都是可行的。但是,实际上只有从 WinXP 开始才可行,因为 Win2K 缺少 WTSGetActiveConsoleSessionId()WTSQueryUserToken() API(以及 WTSEnumerateProcesses())对于 Win2K 专业版)。您可以将 0 硬编码为会话 ID(因为在 Win2K 中总是这种情况),并且我想您可能能够通过枚举正在运行的进程并复制其令牌之一来获取用户令牌(它应该是具有存在交互式 SID)。无论如何,即使您没有从服务设置中选择“与桌面交互”,CreateProcessAsUser() 在传递交互式用户令牌时也会以相同的方式运行。无论如何,它也比直接从服务启动更安全,因为该进程不会继承神圣的 LocalSystem 访问令牌。

现在,我不知道您的第三方应用程序在运行脚本/进程时是否会执行此操作,但如果您想从服务中执行此操作,那就是(对于 Vista 或 Win7,这是唯一的方法)克服会话 0 隔离)。

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:

  1. 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).
  2. Pass the session id from the previous API call to WTSQueryUserToken() to get an open token that reprents the user logged into the console.
  3. Call DuplicateTokenEx() to convert the impersonation token (from WTSQueryUserToken) into a primary token.
  4. Call CreateEnvironmentBlock() to create a new environment for the process (optional, but if you don't, the process won't have one).
  5. 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 the CREATE_UNICODE_ENVIRONMENT flag as well (always). This may seem silly, but the API fails horribly if you don't (with ERROR_INVALID_PARAMTER).
  6. 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.

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).

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.

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).

柒夜笙歌凉 2024-09-01 07:18:26

根据您的用例,您可以执行我所做的操作。我寻找活动会话的 winlogon 进程并窃取其令牌。如果没有活动会话(API 返回 -1),则在 WINVER >= 6 时使用 1,否则使用 0。这会导致活动会话上出现 SYSTEM。

Depending on your use case, you could do what I do. I hunt down the winlogon process for the active session and steal its token. If there's no active session (API returned -1), use 1 if WINVER >= 6 otherwise 0. This results in SYSTEM on the active session.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文