调用 NamedPipeServerStream.SetAccessControl 时“尝试执行未经授权的操作”

Posted

技术标签:

【中文标题】调用 NamedPipeServerStream.SetAccessControl 时“尝试执行未经授权的操作”【英文标题】:"Attempted to perform an unauthorized operation" when calling NamedPipeServerStream.SetAccessControl 【发布时间】:2018-06-06 02:38:29 【问题描述】:

我正在尝试使用PipeSecurity 来保护NamedPipeServerStream。当我在下面的 sn-p 中调用 this.pipeServer.SetAccessControl(pipeSecurity) 时,出现以下异常:

Attempted to perform an unauthorized operation.
    at System.Security.AccessControl.Win32.SetSecurityInfo(ResourceType type, String name, SafeHandle handle, SecurityInfos securityInformation, SecurityIdentifier owner, SecurityIdentifier group, GenericAcl sacl, GenericAcl dacl)
    at System.Security.AccessControl.NativeObjectSecurity.Persist(String name, SafeHandle handle, AccessControlSections includeSections, Object exceptionContext)
    at System.Security.AccessControl.NativeObjectSecurity.Persist(SafeHandle handle, AccessControlSections includeSections, Object exceptionContext)
    at System.IO.Pipes.PipeSecurity.Persist(SafeHandle handle)

代码:

this.pipeServer =
    new NamedPipeServerStream(
        pipeName,
        PipeDirection.InOut,
        1,
        PipeTransmissionMode.Byte,
        PipeOptions.Asynchronous);

PipeSecurity pipeSecurity = new PipeSecurity();

WindowsIdentity identity = WindowsIdentity.GetCurrent();
WindowsPrincipal principal = new WindowsPrincipal(identity);

if (principal.IsInRole(WindowsBuiltInRole.Administrator))

    // Allow the Administrators group full access to the pipe.
    pipeSecurity.AddAccessRule(new PipeAccessRule(
        new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null).Translate(typeof(NTAccount)),
        PipeAccessRights.FullControl, AccessControlType.Allow));
 else 
    // Allow current user read and write access to the pipe.
    pipeSecurity.AddAccessRule(new PipeAccessRule(
        WindowsIdentity.GetCurrent().User,
        PipeAccessRights.ReadWrite, AccessControlType.Allow));


this.pipeServer.SetAccessControl(pipeSecurity);

对我做错了什么有任何想法吗?

这发生在使用 System.IO.AccessControl nuget 包的 .NET Framework(以 net451 为目标)和 .NET Standard 1.6 中:

https://www.nuget.org/packages/System.IO.Pipes.AccessControl/

编辑:

我能够使用 #ifdef 来使用适用于 .NET Framework 的 the following constructor:

public NamedPipeServerStream (string pipeName, System.IO.Pipes.PipeDirection 方向, int maxNumberOfServerInstances, System.IO.Pipes.PipeTransmissionMode transmissionMode, System.IO.Pipes.PipeOptions options, int inBufferSize, int outBufferSize, System.IO.Pipes .PipeSecurity pipeSecurity)

但是,.NET Standard 中不存在此构造函数。我尝试使用添加到 .NET Core 的this function:

PipesAclExtensions.SetAccessControl(PipeStream, PipeSecurity)

但这会产生与之前相同的异常。

I created a gist to show this.

【问题讨论】:

social.msdn.microsoft.com/Forums/vstudio/en-US/… 有帮助吗? Setting named pipe security in a Domain的可能重复 这不是重复的 - 他的目标是 .NET 标准 1.6 - 该版本没有您提供的链接的解决方案中建议的构造函数。 @mjwills 感谢这些链接,它为我修复了 .NET Framework 但正如 user3141326 所述,.NET Standard 中不存在该构造函数。请查看我的编辑。 【参考方案1】:

我刚刚遇到了同样的问题,并试图追踪它。

TL;DR

当前状态(2019 年 2 月)令人难过,但却是事实:它只是不适用于当今 NET 标准中给出的类。

票证参考

issue 30170 “unauthorized operation” on NamedPipeServerStream.SetAccessControl issue 31190 System.IO.Pipes.AccessControl package does not work issue 24040 NamedPipeServerStream: Provide support for WRITE_DAC

在这种情况下也很有趣的可能是与 *nix 相关的

issue 34400 mechanism for lower privileged user to connect to a privileged user's pipe

可能的解决方法

仍然可以使用本机 API 调用来根据需要设置安全性,但这不适合胆小的人。基本上需要执行这些步骤:

设置security descriptor in binary representation 将其连接到SECURITY_ATTRIBUTES structure 创建管道via Windows API CreateNamedPipe() 将生成的句柄包装成SafeHandle 并将其输入正确的NamedPipeServerStream CTOR 变体

PS:至少我们现在可以在代码中查找它撞墙的地方。想象一下 20 年前有这个问题......

【讨论】:

【参考方案2】:

在 NET 5 中,您可以通过如下定义创建可供所有用户帐户使用的 NamedPipeServerStream

PipeSecurity pipeSecurity = new PipeSecurity();
pipeSecurity.AddAccessRule(new PipeAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), PipeAccessRights.ReadWrite, AccessControlType.Allow));
using (var pipe = NamedPipeServerStreamAcl.Create("MyAppPipeName", PipeDirection.InOut, 1, PipeTransmissionMode.Message, PipeOptions.None, 0, 0, pipeSecurity))

    pipe.WaitForConnection();
    // ...

请注意,这仅适用于 Windows 操作系统。

【讨论】:

可以确认此方法也适用于 .NET 6【参考方案3】:

最近在将项目翻译成.Net Core时遇到了同样的问题。

我添加了一个 nuget 包来帮助过渡:https://www.nuget.org/packages/NamedPipeServerStream.NetFrameworkVersion/

该包以 .Net Standard 2.0 为目标,并包含来自 .Net Framework 的原始构造函数(支持 PipeSecurity、HandleInheritability 和 PipeAccessRights)。 我从反编译的代码中恢复了它,没有做任何更改。 完全支持异常,没有代码丢失。 有一个强大的名字。 也提供源代码。
Install-Package NamedPipeServerStream.NetFrameworkVersion
using System.IO.Pipes;

var pipeSecurity = new PipeSecurity();
pipeSecurity.AddAccessRule(new PipeAccessRule(WindowsIdentity.GetCurrent().Owner, PipeAccessRights.ReadWrite, AccessControlType.Allow));

using var serverStream = NamedPipeServerStreamConstructors.New(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous | PipeOptions.WriteThrough, 0, 0, pipeSecurity);

【讨论】:

【参考方案4】:

我已经设法实现了 JensG 提到的可能的解决方法。我使用这个示例来构建它: https://code.msdn.microsoft.com/CSNamedPipeServer-4c760c2c/sourcecode?fileId=21684&pathId=1498714400

    public static class NativeNamedPipeServer
    
        public static SafePipeHandle CreateNamedPipeServer(string pipeName, string sddl)
        
            return NativeMethod.CreateNamedPipe(
                @"\\.\pipe\" + pipeName, // The unique pipe name.
                PipeOpenMode.PIPE_ACCESS_DUPLEX | PipeOpenMode.ASYNCHRONOUS,
                PipeMode.PIPE_TYPE_BYTE,
                1, // Max server instances
                1024 * 16, // Output buffer size
                1024 * 16, // Input buffer size
                NMPWAIT_USE_DEFAULT_WAIT, // Time-out interval
                CreateNativePipeSecurity(sddl) // Pipe security attributes
            );
        

        /// <summary>
        /// The CreateNativePipeSecurity function creates and initializes a new 
        /// SECURITY_ATTRIBUTES object to allow Authenticated Users read and 
        /// write access to a pipe, and to allow the Administrators group full 
        /// access to the pipe.
        /// </summary>
        /// <returns>
        /// A SECURITY_ATTRIBUTES object that allows Authenticated Users read and 
        /// write access to a pipe, and allows the Administrators group full 
        /// access to the pipe.
        /// </returns>
        /// <see cref="http://msdn.microsoft.com/en-us/library/aa365600(VS.85).aspx"/>
        private static SECURITY_ATTRIBUTES CreateNativePipeSecurity(string sddl)
        
            if (!NativeMethod.ConvertStringSecurityDescriptorToSecurityDescriptor(
                sddl, 1, out var pSecurityDescriptor, IntPtr.Zero))
            
                throw new Win32Exception();
            

            SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
            sa.nLength = Marshal.SizeOf(sa);
            sa.lpSecurityDescriptor = pSecurityDescriptor;
            sa.bInheritHandle = false;
            return sa;
        


        #region Native API Signatures and Types

        /// <summary>
        /// Named Pipe Open Modes
        /// http://msdn.microsoft.com/en-us/library/aa365596.aspx
        /// </summary>
        [Flags]
        internal enum PipeOpenMode : uint
        
            PIPE_ACCESS_INBOUND = 0x00000001, // Inbound pipe access.
            PIPE_ACCESS_OUTBOUND = 0x00000002, // Outbound pipe access.
            PIPE_ACCESS_DUPLEX = 0x00000003, // Duplex pipe access.

            // added from C# PipeOptions.cs
            WRITE_THROUGH = 0x80000000,
            ASYNCHRONOUS = 0x40000000,
            CURRENT_USER_ONLY = 0x20000000
        

        /// <summary>
        /// Named Pipe Type, Read, and Wait Modes
        /// http://msdn.microsoft.com/en-us/library/aa365605.aspx
        /// </summary>
        [Flags]
        internal enum PipeMode : uint
        
            // Type Mode
            PIPE_TYPE_BYTE = 0x00000000, // Byte pipe type.
            PIPE_TYPE_MESSAGE = 0x00000004, // Message pipe type.

            // Read Mode
            PIPE_READMODE_BYTE = 0x00000000, // Read mode of type Byte.
            PIPE_READMODE_MESSAGE = 0x00000002, // Read mode of type Message.

            // Wait Mode
            PIPE_WAIT = 0x00000000, // Pipe blocking mode.
            PIPE_NOWAIT = 0x00000001 // Pipe non-blocking mode.
        

        /// <summary>
        /// Uses the default time-out specified in a call to the 
        /// CreateNamedPipe method.
        /// </summary>
        internal const uint NMPWAIT_USE_DEFAULT_WAIT = 0x00000000;


        /// <summary>
        /// The SECURITY_ATTRIBUTES structure contains the security descriptor for 
        /// an object and specifies whether the handle retrieved by specifying 
        /// this structure is inheritable. This structure provides security 
        /// settings for objects created by various functions, such as CreateFile, 
        /// CreateNamedPipe, CreateProcess, RegCreateKeyEx, or RegSaveKeyEx.
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        internal class SECURITY_ATTRIBUTES
        
            public int nLength;
            public SafeLocalMemHandle lpSecurityDescriptor;
            public bool bInheritHandle;
        


        /// <summary>
        /// Represents a wrapper class for a local memory pointer. 
        /// </summary>
        [SuppressUnmanagedCodeSecurity,
         HostProtection(SecurityAction.LinkDemand, MayLeakOnAbort = true)]
        internal sealed class SafeLocalMemHandle : SafeHandleZeroOrMinusOneIsInvalid
        
            public SafeLocalMemHandle() : base(true)
            
            

            public SafeLocalMemHandle(IntPtr preexistingHandle, bool ownsHandle)
                : base(ownsHandle)
            
                base.SetHandle(preexistingHandle);
            

            [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success),
             DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            private static extern IntPtr LocalFree(IntPtr hMem);

            protected override bool ReleaseHandle()
            
                return (LocalFree(base.handle) == IntPtr.Zero);
            
        


        /// <summary>
        /// The class exposes Windows APIs to be used in this code sample.
        /// </summary>
        [SuppressUnmanagedCodeSecurity]
        internal class NativeMethod
        
            /// <summary>
            /// Creates an instance of a named pipe and returns a handle for 
            /// subsequent pipe operations.
            /// </summary>
            /// <param name="pipeName">Pipe name</param>
            /// <param name="openMode">Pipe open mode</param>
            /// <param name="pipeMode">Pipe-specific modes</param>
            /// <param name="maxInstances">Maximum number of instances</param>
            /// <param name="outBufferSize">Output buffer size</param>
            /// <param name="inBufferSize">Input buffer size</param>
            /// <param name="defaultTimeout">Time-out interval</param>
            /// <param name="securityAttributes">Security attributes</param>
            /// <returns>If the function succeeds, the return value is a handle 
            /// to the server end of a named pipe instance.</returns>
            [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            public static extern SafePipeHandle CreateNamedPipe(string pipeName,
                PipeOpenMode openMode, PipeMode pipeMode, int maxInstances,
                int outBufferSize, int inBufferSize, uint defaultTimeout,
                SECURITY_ATTRIBUTES securityAttributes);


            /// <summary>
            /// The ConvertStringSecurityDescriptorToSecurityDescriptor function 
            /// converts a string-format security descriptor into a valid, 
            /// functional security descriptor.
            /// </summary>
            /// <param name="sddlSecurityDescriptor">
            /// A string containing the string-format security descriptor (SDDL) 
            /// to convert.
            /// </param>
            /// <param name="sddlRevision">
            /// The revision level of the sddlSecurityDescriptor string. 
            /// Currently this value must be 1.
            /// </param>
            /// <param name="pSecurityDescriptor">
            /// A pointer to a variable that receives a pointer to the converted 
            /// security descriptor.
            /// </param>
            /// <param name="securityDescriptorSize">
            /// A pointer to a variable that receives the size, in bytes, of the 
            /// converted security descriptor. This parameter can be IntPtr.Zero.
            /// </param>
            /// <returns>
            /// If the function succeeds, the return value is true.
            /// </returns>
            [return: MarshalAs(UnmanagedType.Bool)]
            [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            public static extern bool ConvertStringSecurityDescriptorToSecurityDescriptor(
                string sddlSecurityDescriptor, int sddlRevision,
                out SafeLocalMemHandle pSecurityDescriptor,
                IntPtr securityDescriptorSize);
        

        #endregion
    

创建:

            var safePipeHandle = NativeNamedPipeServer.CreateNamedPipeServer(_pipeName, 
                pipeSecurity.GetSecurityDescriptorSddlForm(AccessControlSections.Access));
            var stream = new NamedPipeServerStream(PipeDirection.InOut, true, false, safePipeHandle);

最棘手的部分是使异步工作,因为原始源没有PipeOpenMode.ASYNCHRONOUS 标志。通过检查 .NET Core 3.0 代码发现了这一点。奇怪的是,他们有所有的管道安全代码,但没有它的构造函数。因此,另一种方法实际上可能是反射。

【讨论】:

这很有效,谢谢。我只需要使用 sddl 来允许匿名用户。搞定了,谢谢。 非常感谢您的努力。这很好用,我更喜欢 NuGet 包方法。如果客户愿意,他们也可以很容易地扩展该方法以允许指定硬编码的参数,例如缓冲区大小和管道打开模式。干得好,先生! 0 我正在尝试运行此代码,但我偶然发现了这一行:pipeSecurity.GetSecurityDescriptorSddlForm()。 pipeSecurity是如何定义的? 查看问题中的代码sn-ps,看看如何创建pipeSecurity对象。

以上是关于调用 NamedPipeServerStream.SetAccessControl 时“尝试执行未经授权的操作”的主要内容,如果未能解决你的问题,请参考以下文章

NamedPipeServerStream 和 WaitForConnection 方法

NamedPipeServerStream/NamedPipeClientStream 包装器

为啥我的 NamedPipeServerStream 不等待?

CallNamedPipe 和 NamedPipeServerStream,访问被拒绝?

NamedPipeServerStream/async 可靠断开问题

限制 NamedPipeServerStream 的范围