配置NTLM认证后RPC服务器命名管道权限未受限的问题排查
Let's break down why your current configuration isn't enforcing the restrictions you want, and walk through the steps to lock down the server to only allow administrators and LocalSystem to connect.
Why Your Current Setup Fails
Right now, two key pieces are missing from your implementation:
- No Authorization Rules: Calling
AddAuthentication(RpcAuthentication.RPC_C_AUTHN_WINNT)enables NTLM authentication, but it doesn't restrict which authenticated users are allowed to access the RPC service. Any valid Windows user can still connect once they pass NTLM. - Permissive Default Pipe ACL: The library is creating the named pipe with a default, wide-open access control list (ACL) that grants
RWto Everyone and Anonymous Logon. This means clients can establish a pipe connection before RPC authentication even runs, bypassing your intended restrictions.
Step 1: Enforce RPC Authentication + Authorization
First, we'll strengthen the RPC server's authentication requirements and add explicit authorization rules to only allow administrators.
// Initialize the server as before server = new RpcServerApi(IId, MaxCalls, ushort.MaxValue, true); // Set strict authentication level to block anonymous connections server.SetAuthenticationLevel(RpcAuthnLevel.RPC_C_AUTHN_LEVEL_PKT_INTEGRITY); // Enable NTLM authentication server.AddAuthentication(RpcAuthentication.RPC_C_AUTHN_WINNT); // Add authorization rule to only allow the Administrators group server.AddAuthorization(RpcAuthorization.RPC_C_AUTHZ_NAME, @"BUILTIN\Administrators"); // Also add LocalSystem explicitly if needed server.AddAuthorization(RpcAuthorization.RPC_C_AUTHZ_NAME, @"NT AUTHORITY\SYSTEM");
Step 2: Override Named Pipe ACL to Restrict Access
Next, we need to replace the default pipe ACL with one that only grants read/write access to administrators and LocalSystem. You can do this either when adding the protocol (if the library supports it) or by manually modifying the pipe's security settings after creation.
Option 1: Pass Custom Security Descriptor to AddProtocol
If your version of CSharpTest.Net.RpcLibrary has an overload of AddProtocol that accepts a security descriptor, use this approach:
// Create a security descriptor that only allows admins and LocalSystem var adminOnlySd = CreateAdminOnlySecurityDescriptor(); // Add the named pipe protocol with the custom security descriptor server.AddProtocol(RpcProtseq.ncacn_np, Id, MaxCalls, adminOnlySd);
Option 2: Manually Update Pipe ACL After Creation
If the library doesn't support passing a security descriptor directly, modify the pipe's permissions after calling AddProtocol:
// Add the protocol first server.AddProtocol(RpcProtseq.ncacn_np, Id, MaxCalls); // Update the pipe's security settings var pipePath = @"\\.\pipe\myNamedPipe"; SetNamedPipeSecurity(pipePath, CreateAdminOnlySecurityDescriptor());
Helper Methods for Security Descriptor
Here's the code to create the restricted security descriptor and apply it to the pipe:
private static SecurityDescriptor CreateAdminOnlySecurityDescriptor() { var sd = new SecurityDescriptor(); var adminSid = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null); var localSystemSid = new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null); // Create access control entries (ACEs) for allowed users/groups var adminAce = new CommonAce( AceFlags.None, AceQualifier.AccessAllowed, (int)PipeAccessRights.ReadWrite, adminSid, false, null ); var systemAce = new CommonAce( AceFlags.None, AceQualifier.AccessAllowed, (int)PipeAccessRights.ReadWrite, localSystemSid, false, null ); // Build the discretionary ACL var dacl = new DiscretionaryAcl(false, false, 2); dacl.AddAce(adminAce); dacl.AddAce(systemAce); sd.DiscretionaryAcl = dacl; sd.ControlFlags = ControlFlags.DiscretionaryAclPresent; return sd; } private static void SetNamedPipeSecurity(string pipePath, SecurityDescriptor sd) { // Open the pipe handle var pipeHandle = CreateFile( pipePath, (uint)FileAccess.ReadWrite, (uint)FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, (uint)FileAttributes.Normal, IntPtr.Zero ); if (pipeHandle == IntPtr.Zero || pipeHandle == new IntPtr(-1)) throw new Win32Exception(); try { // Apply the new security descriptor if (!SetSecurityInfo( pipeHandle, ObjectType.FileObject, SecurityInfos.DiscretionaryAcl, null, null, sd.DiscretionaryAcl, null )) throw new Win32Exception(); } finally { CloseHandle(pipeHandle); } } // Required Windows API imports [DllImport("advapi32.dll", SetLastError = true)] private static extern bool SetSecurityInfo( IntPtr handle, ObjectType objectType, SecurityInfos securityInfo, SecurityIdentifier owner, SecurityIdentifier group, DiscretionaryAcl dacl, SystemAcl sacl ); [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr CreateFile( string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile ); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool CloseHandle(IntPtr hObject); // Enums for API calls public enum ObjectType { FileObject = 1 } [Flags] public enum SecurityInfos { DiscretionaryAcl = 0x00000004 }
Final Verification
After implementing these changes:
- Check the named pipe's ACL using PowerShell (
Get-Acl \\.\pipe\myNamedPipe | Format-List) – it should only show permissions for Administrators and LocalSystem. - Test with a non-admin account: the client should fail to connect (either at the pipe level or during RPC authorization).
- Test with an admin account or LocalSystem: the connection should succeed as expected.
内容的提问来源于stack exchange,提问作者grant-cameron

