C# COM对象如何获取C++调用者令牌并授予文件读写权限?
在C# COM对象中获取C++调用者的安全令牌并设置文件ACL
核心思路
COM调用依赖RPC机制,可通过Windows API获取调用者的安全上下文:先通过CoGetCallContext获取IServerSecurity接口,调用其ImpersonateClient方法模拟调用者身份,再获取当前线程的安全令牌;从令牌提取用户SID后,为新建文件添加该SID的读写权限,避免授予EVERYONE权限。
步骤1:导入必要的Windows API与COM接口
在C#中需要定义IServerSecurity接口(COM内置安全接口)并导入相关系统API:
using System; using System.Runtime.InteropServices; using System.Security.AccessControl; using System.Security.Principal; using System.IO; using System.ComponentModel; [ComImport, Guid("0000013E-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface IServerSecurity { void QueryBlanket( out uint pAuthnLevel, out uint pImpLevel, out IntPtr pPrivs, out uint pAuthnSvc, out IntPtr pAuthzSvc, out IntPtr pAuthInfo, out uint pCapabilities); void ImpersonateClient(); void RevertToSelf(); [PreserveSig] int IsImpersonating(); } // 导入系统API [DllImport("ole32.dll")] private static extern int CoGetCallContext(ref Guid riid, out IntPtr ppv); [DllImport("kernel32.dll")] private static extern IntPtr GetCurrentThread(); [DllImport("advapi32.dll", SetLastError = true)] private static extern bool OpenThreadToken(IntPtr ThreadHandle, uint DesiredAccess, bool OpenAsSelf, out IntPtr TokenHandle); [DllImport("advapi32.dll", SetLastError = true)] private static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle); [DllImport("kernel32.dll")] private static extern IntPtr GetCurrentProcess(); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool CloseHandle(IntPtr hObject); // 令牌访问权限常量 private const uint TOKEN_QUERY = 0x0008; private const uint TOKEN_DUPLICATE = 0x0002;
步骤2:实现获取调用者令牌并设置文件ACL的逻辑
在COM对象的方法中,按流程获取令牌、创建文件并设置权限:
public string CreateRestrictedFile(string filePath) { Guid iidIServerSecurity = typeof(IServerSecurity).GUID; IntPtr pServerSecurity = IntPtr.Zero; IServerSecurity serverSecurity = null; IntPtr callerToken = IntPtr.Zero; try { // 获取COM调用上下文的安全接口 int hr = CoGetCallContext(ref iidIServerSecurity, out pServerSecurity); if (hr != 0) throw new COMException("Failed to get call context", hr); serverSecurity = (IServerSecurity)Marshal.GetObjectForIUnknown(pServerSecurity); // 模拟调用者身份 serverSecurity.ImpersonateClient(); try { // 获取当前线程的令牌(即调用者的令牌) if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_DUPLICATE, false, out callerToken)) { // 线程无令牌时尝试获取进程令牌(兼容低模拟级别场景) if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_DUPLICATE, out callerToken)) throw new Win32Exception(); } } finally { // 恢复COM对象自身身份 serverSecurity.RevertToSelf(); } // 创建目标文件 using (FileStream fs = File.Create(filePath)) {} // 从令牌提取调用者的SID using (WindowsIdentity callerIdentity = new WindowsIdentity(callerToken)) { SecurityIdentifier callerSid = callerIdentity.User; // 构建文件ACL,添加调用者的读写权限 FileSecurity fileAcl = new FileSecurity(); fileAcl.AddAccessRule(new FileSystemAccessRule( callerSid, FileSystemRights.Read | FileSystemRights.Write, InheritanceFlags.None, PropagationFlags.None, AccessControlType.Allow)); // 将ACL应用到文件 File.SetAccessControl(filePath, fileAcl); } return filePath; } finally { // 释放所有资源,避免泄漏 if (callerToken != IntPtr.Zero) CloseHandle(callerToken); if (serverSecurity != null) Marshal.ReleaseComObject(serverSecurity); if (pServerSecurity != IntPtr.Zero) Marshal.Release(pServerSecurity); } }
关键注意事项
- 资源释放:所有获取的COM接口指针、系统令牌必须在
finally块中释放,防止资源泄漏。 - 模拟上下文:必须在
ImpersonateClient后立即获取令牌,并在完成后调用RevertToSelf恢复身份,避免后续代码以调用者身份执行。 - 权限兼容性:如果调用者的COM模拟级别为
Anonymous,OpenThreadToken可能失败,此时降级获取进程令牌(适用于进程内COM调用场景)。 - ACL继承:若文件所在目录存在权限继承规则,需调整
InheritanceFlags和PropagationFlags参数,确保添加的权限优先生效。
内容的提问来源于stack exchange,提问作者jmucchiello
相关产品推荐
相关产品推荐

