返回

Python UAC提权: 非当前管理员账户运行

windows

Python处理用户账户控制(UAC)提升权限问题

挑战:非当前管理员账户运行UAC程序

在使用 Python 调用系统命令或执行程序时,有时候会需要使用一个非当前登录用户的管理员账户来运行一个程序,且该程序可能需要 UAC 提升权限。如果程序启动需要 UAC 权限,则直接使用CreateProcessWithLogonW启动,往往会因为权限不足失败。标准做法例如 ShellExecuteW ,在权限请求时,仍将弹窗要求当前用户确认权限。这时如何实现通过指定的管理员用户执行需要UAC的程序便是一个需要解决的问题。

错误分析:CreateProcessWithLogonWShellExecuteW

现有的Python代码使用 CreateProcessWithLogonW 来创建一个新的进程,此方法可以使用特定的账户凭据来创建进程,但是当所运行的程序请求 UAC 提升时,CreateProcessWithLogonW并不能自动触发弹窗要求提升到所需账户的权限。代码尝试使用 ShellExecuteW 并传入 runas 参数尝试以管理员权限启动程序。这样做仅仅触发 UAC 提升请求弹窗,且该请求作用于当前登录的用户 ,而不是通过CreateProcessWithLogonW传入的用户,因此仍无法实现最初需求。

解决方案一:使用Runas命令配合进程创建

解决该问题的一种思路是利用 runas 命令,配合 CreateProcessWithLogonW 来执行程序。 runas命令可以以指定的身份运行程序,并且如果需要还会触发UAC请求。使用 runas 可以完成提权工作。 使用CreateProcessWithLogonW 使用指定的管理员身份运行runas命令, 之后runas 会执行用户需要的应用程序,并将执行用户更改为管理员账户,从而避开权限不足问题。

操作步骤:

  1. 使用 CreateProcessWithLogonW 创建一个 cmd.exe 进程。
  2. 在创建的 cmd.exe 中,构造一条 runas 命令,目标是运行我们想要运行的程序,同时指定所需要的管理员账户。
  3. 执行 cmd.exe 进程并让其执行构造的 runas 命令。

示例代码如下:

import ctypes
import os
import subprocess

def run_as_admin_with_specified_user(username, password, domain, target_exe, params=""):
    
    command = f'runas /user:"{domain}\\{username}" /savecred "{target_exe} {params}"' # 使用/savecred 可避免重复提示输入密码
    try:
        process = subprocess.Popen(command, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        _, error = process.communicate(input=f'{password}\n'.encode('utf-8'))
        if error:
          raise RuntimeError(f"Error running the command {command}: {error.decode()}")
        else:
          print("程序启动成功")
    except Exception as e:
      raise e
   
if __name__ == "__main__":
    username = "user"
    password = "pass"
    domain = "domain"
    target_executable = r"C:\Windows\System32\notepad.exe"  # 使用管理员账户运行的程序,可以用自己的目标程序替换
    run_as_admin_with_specified_user(username, password, domain, target_executable)

重要提示 : 使用 /savecred 参数可能导致凭证信息存储在 Windows 凭据管理器中,请评估该风险。也可以不用 /savecred 参数,则程序运行会提示用户输入密码,但这样用户在多次运行时可能都需要手动输入。 可以自行评估和决定如何使用此参数。

解决方案二:使用PSEXEC工具

PSEXEC是 Sysinternals Suite 中的一个命令行工具,可以在远程计算机或本地计算机上运行进程。 它还可以使用另一个用户的凭据来运行程序,同时也会提升权限,满足要求。 可以用Python来调用该命令。PSEXEC 可以从微软官方下载获得。

操作步骤:

  1. 下载并安装 PSEXEC。
  2. 使用 Python 代码构造 PSEXEC 命令,并指定所需用户凭据。
  3. 使用subprocess.Popen来执行命令。

代码示例:

import subprocess
import os
def run_with_psexec(username, password, domain, target_exe, params=""):
  
    psexec_path = os.path.join(r"C:\SysinternalsSuite","psexec.exe")  # 更改成psexec的安装目录
    command = f'"{psexec_path}" \\\\localhost -u "{domain}\\{username}" -p "{password}" -accepteula -i "{target_exe}" {params}' # "-accepteula" 可以免除用户接受EULA弹窗提示
    try:
        process = subprocess.Popen(command, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout,error = process.communicate()
        if error:
          raise RuntimeError(f"Error running the command {command}: {error.decode()}")
        else:
           print("程序启动成功")
    except Exception as e:
      raise e

if __name__ == "__main__":
  username = "user"
  password = "pass"
  domain = "domain"
  target_executable = r"C:\Windows\System32\regedit.exe" #需要使用管理员权限的程序, 可根据需求自行更换
  run_with_psexec(username, password, domain, target_executable)

注意 : PSEXEC 通常在域环境中用于远程管理,务必谨慎评估安全性,并确保运行 PSEXEC 的账户拥有足够的权限。-accepteula 参数可以绕过接受 PSEXEC 的许可协议的提示框。 确保了解风险后选择是否使用该参数。

额外安全建议

无论选择哪种方案,使用非当前用户的管理员凭据启动程序始终存在一定的安全风险。务必确保以下事项:

  • 最小权限原则:只使用执行特定任务所需的权限。
  • 凭据安全: 不应将凭据直接硬编码到代码中,而是应通过安全方式获取,例如使用凭证管理器或者环境变量,以及做好相关加解密措施。
  • 代码审查:仔细检查并测试代码,以减少漏洞。
  • 安全意识:谨慎使用此功能, 并注意UAC相关的权限和用户行为。

以上提供的两种解决方案都通过创建独立的进程运行指定应用程序来实现需求。实际使用时应综合考虑自身的需求和安全级别,权衡选择合适的方案。