基于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 "); }




核心问题
你当前代码的关键错误是:获取的是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
相关产品推荐
相关产品推荐

