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

基于Waffle、Spring Security、JNA的模拟用户进程创建异常排查

问题:模拟登录用户创建进程失败,进程所有者为服务器用户

当用户通过Web应用访问控制器方法时,执行下方代码模拟登录用户身份创建进程。代码无报错且成功创建记事本进程,但进程所有者为服务器用户而非登录用户。需要指出代码缺失部分,实现以模拟用户身份创建进程。

注:已以管理员身份运行Eclipse/STS,主令牌(processToken)具备模拟所需的全部权限。

public String createProcessAsUser(HttpServletRequest req) {

        // 为所有函数启用错误处理——无错误
        HANDLEByReference processToken = new HANDLEByReference();
        HANDLE processHandle = Kernel32.INSTANCE.GetCurrentProcess();

        // 获取当前进程句柄——活动用户的访问令牌
        if (!Advapi32.INSTANCE.OpenProcessToken(
                processHandle, WinNT.TOKEN_DUPLICATE | WinNT.TOKEN_QUERY | WinNT.TOKEN_ALL_ACCESS
                        | WinNT.TOKEN_ASSIGN_PRIMARY | WinNT.TOKEN_ADJUST_PRIVILEGES | WinNT.TOKEN_IMPERSONATE,
                processToken)) {
            int rc = Kernel32.INSTANCE.GetLastError();
            if (rc != W32Errors.ERROR_NO_TOKEN) {
                throw new Win32Exception(rc);
            }
        }

        WinNT.LUID luid = null;
        WinNT.TOKEN_PRIVILEGES tp = new WinNT.TOKEN_PRIVILEGES(2);

        /*
         * 模拟分配令牌所需的权限。
           用户无SE_ASSIGNPRIMARYTOKEN_NAME权限,因此无法启用/禁用
         * 
         */
        String[] privileges = { WinNT.SE_INCREASE_QUOTA_NAME, WinNT.SE_ASSIGNPRIMARYTOKEN_NAME };

        for (int i = 0; i < privileges.length; i++) {
            luid = new WinNT.LUID();
            if (Advapi32.INSTANCE.LookupPrivilegeValue("", privileges[i], luid)) {
                tp.Privileges[i] = new LUID_AND_ATTRIBUTES(luid, new DWORD(WinNT.SE_PRIVILEGE_ENABLED));
            } else {
                throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
            }
        }

        // 为现有进程句柄/令牌启用权限
        if (!Advapi32.INSTANCE.AdjustTokenPrivileges(processToken.getValue(), false, tp, tp.size(), null,
                new IntByReference())) {
            Win32Exception error = new Win32Exception(Kernel32.INSTANCE.GetLastError());
            System.out.println(error.getMessage());
            throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
        }

        // 模拟登录用户
        if (!Advapi32.INSTANCE.ImpersonateLoggedOnUser(processToken.getValue())) { /* 此方法返回true */
            throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
        }

        WinNT.HANDLEByReference primaryToken = new WinNT.HANDLEByReference();

        // 复制模拟令牌以创建主令牌,用于调用CreateProcessAsUser
        if (!Advapi32.INSTANCE.DuplicateTokenEx(processToken.getValue(),
                WinNT.TOKEN_DUPLICATE | WinNT.TOKEN_QUERY | WinNT.TOKEN_ASSIGN_PRIMARY | WinNT.TOKEN_ADJUST_DEFAULT
                        | WinNT.TOKEN_ALL_ACCESS,
                null, WinNT.SECURITY_IMPERSONATION_LEVEL.SecurityDelegation, WinNT.TOKEN_TYPE.TokenPrimary,
                primaryToken)) {
            throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
        }

        WinBase.STARTUPINFO startupInfo = new WinBase.STARTUPINFO();
        WinBase.PROCESS_INFORMATION processInfo = new WinBase.PROCESS_INFORMATION();
        if (!Advapi32.INSTANCE.CreateProcessAsUser(primaryToken.getValue(), "C:\\Windows\\notepad.exe", null, null,
                null, false, WinNT.CREATE_NEW_CONSOLE | WinNT.CREATE_UNICODE_ENVIRONMENT, null, null, startupInfo,
                processInfo)) {
            throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
        }
        return String.format("New Process created ");
    }

进程资源管理器中的记事本进程权限
令牌分配主权限
SeAssignPrimaryTokenPrivilege权限
用户权限分配


核心问题

你当前代码的关键错误是:获取的是Web服务器进程自身的令牌,而非登录用户的身份令牌。GetCurrentProcess()拿到的是Web服务器(如Tomcat)的进程句柄,后续OpenProcessToken获取的是服务器进程的令牌,完全和当前请求的登录用户无关。

缺失的关键步骤

1. 从请求中获取登录用户的Windows身份令牌

替换原有的GetCurrentProcess和OpenProcessToken逻辑,直接从HttpServletRequest提取登录用户的令牌:

// 替换原获取processToken的代码
WindowsPrincipal principal = (WindowsPrincipal) req.getUserPrincipal();
WindowsIdentity identity = (WindowsIdentity) principal.getIdentity();
HANDLE userToken = identity.getToken();
HANDLEByReference processToken = new HANDLEByReference(userToken);

这段代码直接拿到当前请求对应的登录用户的Windows身份令牌,而非服务器进程的令牌。

2. 调整令牌复制的安全级别

调用DuplicateTokenEx时,将SECURITY_IMPERSONATION_LEVEL改为SecurityImpersonation(除非有跨机器委托需求),更符合常规本地模拟场景。

3. 结束模拟状态并清理资源

进程创建完成后,调用RevertToSelf()结束模拟,避免影响后续请求处理;同时记得关闭令牌句柄防止资源泄漏:

// CreateProcessAsUser成功后添加
Advapi32.INSTANCE.RevertToSelf();
Kernel32.INSTANCE.CloseHandle(primaryToken.getValue());
Kernel32.INSTANCE.CloseHandle(processToken.getValue());

修正后的完整代码片段

public String createProcessAsUser(HttpServletRequest req) {
    // 为所有函数启用错误处理——无错误
    HANDLEByReference processToken = new HANDLEByReference();

    // 关键修正:获取当前登录用户的Windows身份令牌
    WindowsPrincipal principal = (WindowsPrincipal) req.getUserPrincipal();
    WindowsIdentity identity = (WindowsIdentity) principal.getIdentity();
    HANDLE userToken = identity.getToken();
    processToken.setValue(userToken);

    WinNT.LUID luid = null;
    WinNT.TOKEN_PRIVILEGES tp = new WinNT.TOKEN_PRIVILEGES(2);

    /*
     * 模拟分配令牌所需的权限。
       用户无SE_ASSIGNPRIMARYTOKEN_NAME权限,因此无法启用/禁用
     */
    String[] privileges = { WinNT.SE_INCREASE_QUOTA_NAME, WinNT.SE_ASSIGNPRIMARYTOKEN_NAME };

    for (int i = 0; i < privileges.length; i++) {
        luid = new WinNT.LUID();
        if (Advapi32.INSTANCE.LookupPrivilegeValue("", privileges[i], luid)) {
            tp.Privileges[i] = new LUID_AND_ATTRIBUTES(luid, new DWORD(WinNT.SE_PRIVILEGE_ENABLED));
        } else {
            throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
        }
    }

    // 为现有进程句柄/令牌启用权限
    if (!Advapi32.INSTANCE.AdjustTokenPrivileges(processToken.getValue(), false, tp, tp.size(), null,
            new IntByReference())) {
        Win32Exception error = new Win32Exception(Kernel32.INSTANCE.GetLastError());
        System.out.println(error.getMessage());
        throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
    }

    WinNT.HANDLEByReference primaryToken = new WinNT.HANDLEByReference();

    // 复制模拟令牌以创建主令牌,用于调用CreateProcessAsUser
    if (!Advapi32.INSTANCE.DuplicateTokenEx(processToken.getValue(),
            WinNT.TOKEN_DUPLICATE | WinNT.TOKEN_QUERY | WinNT.TOKEN_ASSIGN_PRIMARY | WinNT.TOKEN_ADJUST_DEFAULT
                    | WinNT.TOKEN_ALL_ACCESS,
            null, WinNT.SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, WinNT.TOKEN_TYPE.TokenPrimary,
            primaryToken)) {
        throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
    }

    WinBase.STARTUPINFO startupInfo = new WinBase.STARTUPINFO();
    WinBase.PROCESS_INFORMATION processInfo = new WinBase.PROCESS_INFORMATION();
    if (!Advapi32.INSTANCE.CreateProcessAsUser(primaryToken.getValue(), "C:\\Windows\\notepad.exe", null, null,
            null, false, WinNT.CREATE_NEW_CONSOLE | WinNT.CREATE_UNICODE_ENVIRONMENT, null, null, startupInfo,
            processInfo)) {
        throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
    }

    // 结束模拟并清理句柄
    Advapi32.INSTANCE.RevertToSelf();
    Kernel32.INSTANCE.CloseHandle(primaryToken.getValue());
    Kernel32.INSTANCE.CloseHandle(processToken.getValue());

    return String.format("New Process created ");
}

补充说明

  • 确保Web应用已启用Windows身份验证,禁用匿名访问,否则req.getUserPrincipal()无法返回有效WindowsPrincipal。
  • 若使用Spring等框架,需配置对应的身份验证过滤器,确保Windows身份信息传递到请求中。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 01:45:31