以低完整性级别打开命名管道

Posted

技术标签:

【中文标题】以低完整性级别打开命名管道【英文标题】:Opening a named pipe in low integrity level 【发布时间】:2010-07-19 15:17:04 【问题描述】:

我正在开发一个由两个模块组成的应用程序。这些模块在以下环境中通过命名管道进行通信:

Windows 7 家庭高级版 x64 Visual Studio 2008 C#/.Net 3.5

服务器以管理员权限运行(高完整性级别)。客户端以低完整性级别运行。为了使客户端可以连接到服务器,我需要创建低完整性级别的管道。只有当服务器以中等完整性级别运行时,我才能做到这一点。

我测试了以下设置:

    服务器:高,客户端:低 => 访问被拒绝 服务器:高,客户端:中 => 访问被拒绝 服务器:高,客户端:高 => 好的 服务器:中,客户端:低 => 好的 服务器:中等,客户端:中等 => 好的 服务器:低,客户端:低 => 好的

设置 #4 显示命名管道的创建具有与进程之一不同的完整性级别,这很好。但是,我感兴趣的设置是第一个。

我有一个易于测试的样本。如果连接成功,客户端会写入“已连接”,服务器会写入“已接收连接”。如果连接失败,客户端写入“Failed”,服务器保持“Waiting”状态。

这是我执行客户端程序的方式(对于服务器,只需将 NamePipeClient 替换为 NamedPipeServer):

中等完整性等级: 打开命令提示符

icacls NamedPipeClient.exe /setintegritylevel 中

NamedPipeClient.exe

低完整性级别: 打开命令提示符

icacls NamedPipeClient.exe /setintegritylevel 低

NamedPipeClient.exe

高完整性级别: 在管理员模式下打开命令提示符

icacls NamedPipeClient.exe /setintegritylevel 高

NamedPipeClient.exe

任何帮助将不胜感激!

服务器代码

程序.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Win32.SafeHandles;
using System.IO.Pipes;

namespace NamedPipeServer

    class Program
    
        static void Main(string[] args)
        
            SafePipeHandle handle = LowIntegrityPipeFactory.CreateLowIntegrityNamedPipe("NamedPipe/Test");
            NamedPipeServerStream pipeServer = new NamedPipeServerStream(PipeDirection.InOut, true, false, handle);
            pipeServer.BeginWaitForConnection(HandleConnection, pipeServer);

            Console.WriteLine("Waiting...");
            Console.ReadLine();
        

        private static void HandleConnection(IAsyncResult ar)
        
            Console.WriteLine("Received connection");
        
    

LowIntegrityPipeFactory.cs

using System;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
using System.IO.Pipes;
using System.ComponentModel;
using System.IO;
using System.Security.Principal;
using System.Security.AccessControl;

namespace NamedPipeServer

    static class LowIntegrityPipeFactory
    
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern SafePipeHandle CreateNamedPipe(string pipeName, int openMode,
            int pipeMode, int maxInstances, int outBufferSize, int inBufferSize, int defaultTimeout,
            SECURITY_ATTRIBUTES securityAttributes);

        [DllImport("Advapi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = false)]
        private static extern bool ConvertStringSecurityDescriptorToSecurityDescriptor(
            [In] string StringSecurityDescriptor,
            [In] uint StringSDRevision,
            [Out] out IntPtr SecurityDescriptor,
            [Out] out int SecurityDescriptorSize
        );

        [StructLayout(LayoutKind.Sequential)]
        private struct SECURITY_ATTRIBUTES
        
            public int nLength;
            public IntPtr lpSecurityDescriptor;
            public int bInheritHandle;
        

        private const string LOW_INTEGRITY_SSL_SACL = "S:(ML;;NW;;;LW)";

        public static SafePipeHandle CreateLowIntegrityNamedPipe(string pipeName)
        
            // convert the security descriptor
            IntPtr securityDescriptorPtr = IntPtr.Zero;
            int securityDescriptorSize = 0;
            bool result = ConvertStringSecurityDescriptorToSecurityDescriptor(
                LOW_INTEGRITY_SSL_SACL, 1, out securityDescriptorPtr, out securityDescriptorSize);
            if (!result)
                throw new Win32Exception(Marshal.GetLastWin32Error());

            SECURITY_ATTRIBUTES securityAttributes = new SECURITY_ATTRIBUTES();
            securityAttributes.nLength = Marshal.SizeOf(securityAttributes);
            securityAttributes.bInheritHandle = 1;
            securityAttributes.lpSecurityDescriptor = securityDescriptorPtr;

            SafePipeHandle handle = CreateNamedPipe(@"\\.\pipe\" + pipeName,
                PipeDirection.InOut, 100, PipeTransmissionMode.Byte, PipeOptions.Asynchronous,
                0, 0, PipeAccessRights.ReadWrite, securityAttributes);
            if (handle.IsInvalid)
                throw new Win32Exception(Marshal.GetLastWin32Error());

            return handle;
        

        private static SafePipeHandle CreateNamedPipe(string fullPipeName, PipeDirection direction,
            int maxNumberOfServerInstances, PipeTransmissionMode transmissionMode, PipeOptions options,
            int inBufferSize, int outBufferSize, PipeAccessRights rights, SECURITY_ATTRIBUTES secAttrs)
        
            int openMode = (int)direction | (int)options;
            int pipeMode = 0;
            if (maxNumberOfServerInstances == -1)
                maxNumberOfServerInstances = 0xff;

            SafePipeHandle handle = CreateNamedPipe(fullPipeName, openMode, pipeMode,
                maxNumberOfServerInstances, outBufferSize, inBufferSize, 0, secAttrs);
            if (handle.IsInvalid)
                throw new Win32Exception(Marshal.GetLastWin32Error());
            return handle;
        

    

客户端代码

程序.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Pipes;

namespace NamedPipeClient

    class Program
    
        static void Main(string[] args)
        
            try
            
                var pipeClient = new NamedPipeClientStream(".", "NamedPipe/Test",
                    PipeDirection.InOut,
                    PipeOptions.None);
                pipeClient.Connect(100);
            
            catch (Exception ex)
            
                Console.WriteLine("Failed: " + ex);
                return;
            

            Console.WriteLine("Connected");
            Console.ReadLine();
        
    

【问题讨论】:

问题可能出在“S:(ML;;NW;;;LW)”的 LOW_INTEGRITY_SSL_SACL 字符串中。 NW“表示阻止较低完整性级别的进程写入较高完整性级别的对象(SDDL_NO_WRITE_UP)。”删除 NW 可能会解决您的问题。 你解决问题了吗?我尝试了几种解决方案,但在完全禁用 UAC 的 Windows 7 SP1 Enterprise 64 位上,Low 管道无论如何都无法访问安全 SID 较低的 Medium 管道。 @DzmitryLahoda:不,我没能解决它。我不得不以不同的方式进行操作,并以中等完整性级别运行服务器。这实际上是一种更安全的方法,因为最终没有真正需要以管理员模式运行服务器。 经过几天的黑客攻击***.com/questions/3282365/…,我设法解决了类似的问题 请注意,Windows XP 不支持完整性级别。因此,例如对 ConvertStringSecurityDescriptorToSecurityDescriptor() 的调用将失败,Windows 代码为 1804(数据类型不正确)。 - 不明显。如果您在 Windows XP 上运行代码,希望这可以为您节省一两分钟。 【参考方案1】:

适用于 Windows 7 SP1

public static class NativeMethods

    public const string LOW_INTEGRITY_SSL_SACL = "S:(ML;;NW;;;LW)";

    public static int ERROR_SUCCESS = 0x0;

    public const int LABEL_SECURITY_INFORMATION = 0x00000010;

    public enum SE_OBJECT_TYPE
    
        SE_UNKNOWN_OBJECT_TYPE = 0,
        SE_FILE_OBJECT,
        SE_SERVICE,
        SE_PRINTER,
        SE_REGISTRY_KEY,
        SE_LMSHARE,
        SE_KERNEL_OBJECT,
        SE_WINDOW_OBJECT,
        SE_DS_OBJECT,
        SE_DS_OBJECT_ALL,
        SE_PROVIDER_DEFINED_OBJECT,
        SE_WMIGUID_OBJECT,
        SE_REGISTRY_WOW64_32KEY
    



    [DllImport("advapi32.dll", EntryPoint = "ConvertStringSecurityDescriptorToSecurityDescriptorW")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern Boolean ConvertStringSecurityDescriptorToSecurityDescriptor(
        [MarshalAs(UnmanagedType.LPWStr)] String strSecurityDescriptor,
        UInt32 sDRevision,
        ref IntPtr securityDescriptor,
        ref UInt32 securityDescriptorSize);

    [DllImport("kernel32.dll", EntryPoint = "LocalFree")]
    public static extern UInt32 LocalFree(IntPtr hMem);

    [DllImport("Advapi32.dll", EntryPoint = "SetSecurityInfo")]
    public static extern int SetSecurityInfo(SafeHandle hFileMappingObject,
                                                SE_OBJECT_TYPE objectType,
                                                Int32 securityInfo,
                                                IntPtr psidOwner,
                                                IntPtr psidGroup,
                                                IntPtr pDacl,
                                                IntPtr pSacl);
    [DllImport("advapi32.dll", EntryPoint = "GetSecurityDescriptorSacl")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern Boolean GetSecurityDescriptorSacl(
        IntPtr pSecurityDescriptor,
        out IntPtr lpbSaclPresent,
        out IntPtr pSacl,
        out IntPtr lpbSaclDefaulted);


public class InterProcessSecurity


    public static void SetLowIntegrityLevel(SafeHandle hObject)
    
        IntPtr pSD = IntPtr.Zero;
        IntPtr pSacl;
        IntPtr lpbSaclPresent;
        IntPtr lpbSaclDefaulted;
        uint securityDescriptorSize = 0;

        if (NativeMethods.ConvertStringSecurityDescriptorToSecurityDescriptor(NativeMethods.LOW_INTEGRITY_SSL_SACL, 1, ref pSD, ref securityDescriptorSize))
        
            if (NativeMethods.GetSecurityDescriptorSacl(pSD, out lpbSaclPresent, out pSacl, out lpbSaclDefaulted))
            
                var err = NativeMethods.SetSecurityInfo(hObject,
                                              NativeMethods.SE_OBJECT_TYPE.SE_KERNEL_OBJECT,
                                              NativeMethods.LABEL_SECURITY_INFORMATION,
                                              IntPtr.Zero,
                                              IntPtr.Zero,
                                              IntPtr.Zero,
                                              pSacl);
                if (err != NativeMethods.ERROR_SUCCESS)
                
                    throw new Win32Exception(err);
                
            
            NativeMethods.LocalFree(pSD);
        
    

服务器端设置

   InterProcessSecurity.SetLowIntegrityLevel(pipeServer.SafePipeHandle);

【讨论】:

【参考方案2】:

您在管道上设置强制完整性标签的代码成功实现了这一点。但是,因为您的安全描述符没有定义 DACL,所以您的管道是使用默认值创建的。

当尝试连接到您的高完整性服务器创建的管道时,导致低完整性客户端失败的是 DACL。

您需要在打开监听器之前修复服务器中的 DACL。与其在创建管道之前尝试使用 P/Invoke 代码构建完整的描述符,这很难做到,我建议利用 System.IO.Pipes 类在托管代码中作为一个单独的步骤来完成管道已创建,如下所示:

    // Fix up the DACL on the pipe before opening the listener instance
    // This won't disturb the SACL containing the mandatory integrity label
    NamedPipeServerStream handleForSecurity = null;
    try
    
        handleForSecurity = new NamedPipeServerStream("NamedPipe/Test", PipeDirection.InOut, -1, PipeTransmissionMode.Byte, PipeOptions.None, 0, 0, null, System.IO.HandleInheritability.None, PipeAccessRights.ChangePermissions);
        PipeSecurity ps = handleForSecurity.GetAccessControl();
        PipeAccessRule aceClients = new PipeAccessRule(
            new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null), // or some other group defining the allowed clients
            PipeAccessRights.ReadWrite, 
            AccessControlType.Allow);
        PipeAccessRule aceOwner = new PipeAccessRule(
            WindowsIdentity.GetCurrent().Owner,
            PipeAccessRights.FullControl,
            AccessControlType.Allow);
        ps.AddAccessRule(aceClients);
        ps.AddAccessRule(aceOwner);
        handleForSecurity.SetAccessControl(ps);
    
    finally
    
        if (null != handleForSecurity) handleForSecurity.Close();
        handleForSecurity = null;
    

这对我有用,其余代码不变。

【讨论】:

我无法让它工作。你能链接到一个可运行的例子吗? @HappyNomad:将我的答案中的代码粘贴到 OP 的 Main 方法中,在第一行和第二行之间,即在他从管道工厂类获得句柄之后,但在他实例化 NamedPipeServerStream 之前。 我仍然收到 AccessValidationException。请看:sites.google.com/site/happynomad121/CustomPipeServer.cs 你试过用我的补丁运行 OP 代码吗?它对我有用(在 Vista Business SP2 上)——也就是说,在所有完整性组合中,给 OP 的预期结果“连接”。我还扩展了他的代码来编写来自客户端的消息并在服务器中接收它。一切正常。您的代码不同,并且您提供的内容无法编译,因此我没有花任何时间。如果您的方案还有其他问题,请提出新的 SO 问题。 Chris,请提供您的解决方案的链接,包括从客户端向服务器发送消息的部分。【参考方案3】:

我在 12 月发布的答案确实有效,尽管有人沉迷于匿名驾车投票。(至少,它在 Vista SP2 上有效,我认为 Vista 和 Windows 7 之间没有任何区别这会影响这个问题)。

这是另一种也有效的方法,在管道工厂类中使用的 SDDL 字符串中指定 DACL:

更改调用 ConvertStringSecurityDescriptorToSecurityDescriptor 的 CreateLowIntegrityNamedPipe(string pipeName) 方法中的行,因此:

bool result = ConvertStringSecurityDescriptorToSecurityDescriptor(
     CreateSddlForPipeSecurity(), 1, out securityDescriptorPtr, 
     out securityDescriptorSize);

并提供额外的私有静态方法,例如:

    private static string CreateSddlForPipeSecurity()
    
        const string LOW_INTEGRITY_LABEL_SACL = "S:(ML;;NW;;;LW)";
        const string EVERYONE_CLIENT_ACE = "(A;;0x12019b;;;WD)";
        const string CALLER_ACE_TEMPLATE = "(A;;0x12019f;;;0)";

        StringBuilder sb = new StringBuilder();
        sb.Append(LOW_INTEGRITY_LABEL_SACL);
        sb.Append("D:");
        sb.Append(EVERYONE_CLIENT_ACE);
        sb.AppendFormat(CALLER_ACE_TEMPLATE, WindowsIdentity.GetCurrent().Owner.Value);
        return sb.ToString();
    

我的版本将管道访问权限设置为允许任何经过身份验证的用户成为管道客户端。您可以向管道工厂类添加其他功能,以指定允许的客户端 SID 列表等。

【讨论】:

以上是关于以低完整性级别打开命名管道的主要内容,如果未能解决你的问题,请参考以下文章

如何通过命名管道传输文件描述符

为啥只读打开命名管道块?

打开命名管道时,构造函数上的 FileInputStream 块

Linux:打开命名管道进行写入时超时

无法打开 Windows 命名管道进行写入?

自动打开命名管道和 tcp\ip