You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从OpenSSH会话在Windows用户会话启动进程遇权限拒绝问题

SSH远程执行时用CreateProcessAsUser启动交互进程遇拒绝访问的解决方法

核心问题分析

你的场景和服务启动交互进程类似,但SSH会话有个关键差异:远程SSH登录的会话是无桌面的终端会话,即使账号相同,它的进程上下文和用户本地登录的交互会话(带winsta0桌面)完全隔离。出现CreateProcessAsUser错误5(拒绝访问),主要是令牌权限不足、桌面访问权限缺失、会话未正确关联这几个原因。

必须修正的几个点

1. 显式启用必要特权

你的administrator账号虽然配置了相关权限,但SSH进程默认不会主动启用这些特权。在复制令牌前,必须通过AdjustTokenPrivilegesAPI启用以下特权:

  • SeAssignPrimaryTokenPrivilege(分配主令牌权限)
  • SeIncreaseQuotaPrivilege(增加配额权限)
  • SeDebugPrivilege(调试权限,用于打开用户会话中的explorer进程)

2. 调整令牌复制参数

DuplicateTokenEx的SECURITY_IMPERSONATION_LEVEL应改为SecurityDelegation,而非SecurityImpersonation——因为需要跨会话使用复制后的令牌;同时,请求的令牌权限至少要包含TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY,避免权限过度请求的同时保证足够权限。

3. 确保访问目标桌面权限

SSH进程默认没有访问winsta0\default桌面的权限,需要先打开桌面对象并设置访问权限,确保新进程能正常关联到用户桌面。

4. 确认会话ID关联

复制的explorer令牌已包含目标会话ID,但需确保CreateProcessAsUser创建的进程继承该会话ID,无需额外修改,但需保证获取的explorer进程确实属于目标用户的交互会话。

修正后的代码示例

// 补充必要的Win32常量和结构体声明
const uint TOKEN_QUERY = 0x0008;
const uint TOKEN_DUPLICATE = 0x0002;
const uint TOKEN_ASSIGN_PRIMARY = 0x0001;
const uint TOKEN_ADJUST_PRIVILEGES = 0x0020;
const uint SE_PRIVILEGE_ENABLED = 0x00000002;
const uint DESKTOP_CREATEWINDOW = 0x0002;
const uint DESKTOP_WRITEOBJECTS = 0x0008;
const uint DESKTOP_READOBJECTS = 0x0001;
const int UOI_USER_SID = 4;
const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;

enum SECURITY_IMPERSONATION_LEVEL
{
    SecurityAnonymous,
    SecurityIdentification,
    SecurityImpersonation,
    SecurityDelegation
}

enum TOKEN_TYPE
{
    TokenPrimary = 1,
    TokenImpersonation = 2
}

[StructLayout(LayoutKind.Sequential)]
struct LUID
{
    public uint LowPart;
    public int HighPart;
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct TOKEN_PRIVILEGES
{
    public int PrivilegeCount;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
    public LUID_AND_ATTRIBUTES[] Privileges;
}

[StructLayout(LayoutKind.Sequential)]
struct LUID_AND_ATTRIBUTES
{
    public LUID Luid;
    public uint Attributes;
}

// Win32 API导入(仅补充新增部分,其余API你已有)
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle);

[DllImport("advapi32.dll", SetLastError = true)]
static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, ref LUID lpLuid);

[DllImport("advapi32.dll", SetLastError = true)]
static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, int BufferLength, IntPtr PreviousState, IntPtr ReturnLength);

[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr OpenDesktop(string lpszDesktop, int dwFlags, bool fInherit, uint dwDesiredAccess);

[DllImport("user32.dll", SetLastError = true)]
static extern bool SetUserObjectInformation(IntPtr hObj, int nIndex, IntPtr pvInfo, uint dwInfoLen);

[DllImport("kernel32.dll")]
static extern IntPtr GetCurrentProcess();

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool CloseHandle(IntPtr hObject);

// 启用特权的辅助方法
private static bool EnablePrivilege(string privilegeName)
{
    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out IntPtr tokenHandle))
        return false;

    var luid = new LUID();
    if (!LookupPrivilegeValue(null, privilegeName, ref luid))
    {
        CloseHandle(tokenHandle);
        return false;
    }

    var tp = new TOKEN_PRIVILEGES();
    tp.PrivilegeCount = 1;
    tp.Privileges = new LUID_AND_ATTRIBUTES[1];
    tp.Privileges[0].Luid = luid;
    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

    var result = AdjustTokenPrivileges(tokenHandle, false, ref tp, Marshal.SizeOf(tp), IntPtr.Zero, IntPtr.Zero);
    CloseHandle(tokenHandle);
    return result != false && Marshal.GetLastWin32Error() == 0;
}

// 主执行逻辑
public static void Main(string[] args)
{
    // 替换为目标用户会话中explorer的PID
    int explorerPID = 1234;

    // 先启用必要特权
    EnablePrivilege("SeAssignPrimaryTokenPrivilege");
    EnablePrivilege("SeIncreaseQuotaPrivilege");
    EnablePrivilege("SeDebugPrivilege");

    // 打开explorer进程
    var pHandle = OpenProcess(0x001F0FFF, false, explorerPID);
    if (pHandle == IntPtr.Zero)
    {
        Console.WriteLine($"OpenProcess failed: {Marshal.GetLastWin32Error()}");
        return;
    }

    // 打开进程令牌(请求必要权限)
    if (!OpenProcessToken(pHandle, TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY, out IntPtr tHandle))
    {
        Console.WriteLine($"OpenProcessToken failed: {Marshal.GetLastWin32Error()}");
        CloseHandle(pHandle);
        return;
    }

    // 复制令牌(使用SecurityDelegation)
    var lpT = new SECURITY_ATTRIBUTES();
    if (!DuplicateTokenEx(
            tHandle,
            TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY,
            ref lpT,
            SECURITY_IMPERSONATION_LEVEL.SecurityDelegation,
            TOKEN_TYPE.TokenPrimary,
            out IntPtr newtHandle))
    {
        Console.WriteLine($"DuplicateTokenEx failed: {Marshal.GetLastWin32Error()}");
        CloseHandle(tHandle);
        CloseHandle(pHandle);
        return;
    }

    // 打开并设置桌面权限
    IntPtr hDesktop = OpenDesktop("default", 0, true, DESKTOP_CREATEWINDOW | DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS);
    if (hDesktop != IntPtr.Zero)
    {
        SetUserObjectInformation(hDesktop, UOI_USER_SID, IntPtr.Zero, 0);
        CloseHandle(hDesktop);
    }

    // 启动notepad进程
    var startInfo = new STARTUPINFO();
    var lpProcAttr = new SECURITY_ATTRIBUTES();
    var lpThreadAttr = new SECURITY_ATTRIBUTES();
    startInfo.lpDesktop = "winsta0\\default";
    startInfo.cb = Marshal.SizeOf(startInfo);
    
    if (!CreateProcessAsUser(newtHandle,
           "c:\\Windows\\notepad.exe",
           null,
           ref lpProcAttr,
           ref lpThreadAttr,
           false,
           CREATE_UNICODE_ENVIRONMENT,
           IntPtr.Zero,
           null,
           ref startInfo,
           out PROCESS_INFORMATION pi))
    {
        Console.WriteLine($"CreateProcessAsUser failed: {Marshal.GetLastWin32Error()}");
    }
    else
    {
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
        Console.WriteLine("Notepad started successfully");
    }

    // 清理句柄
    CloseHandle(newtHandle);
    CloseHandle(tHandle);
    CloseHandle(pHandle);
}

额外配置注意事项

  • OpenSSH服务器配置:确保sshd_config中PermitTTY yes(默认开启),AllowUsers包含你的administrator账号,未禁用交互会话。
  • 用户权限分配:在本地安全策略的「用户权限分配」中,确保administrator账号拥有「替换进程级令牌」和「调整进程的内存配额」权限。
  • 会话校验:用query user命令确认目标用户的会话ID,确保获取的explorer进程确实属于该会话。

可行性确认

这个需求完全可行,主流远程管理工具的跨会话交互进程启动功能,核心逻辑和你的思路一致,只要处理好令牌权限、桌面访问和会话关联这几个关键点即可正常运行。

内容的提问来源于stack exchange,提问作者cristopher

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 05:24:11