Charteris Community Server

Welcome to the Charteris plc Community
Welcome to Charteris Community Server Sign in | Join | Help
in Search

Chris Dickson's Blog

Exploring the WCF Named Pipe Binding – Part 4

Technorati Tags: ,

In my previous post in this series, far longer ago than I intended, I showed how the WCF named pipe binding could be tweaked so as to base service security on Windows OS-level access control for the named pipe object used as the transport. Using my AclSecuredNamedPipeBinding can improve the security of WCF services exposed via named pipes but, as I explained, does not completely remove the vulnerability created by the security defect in the standard WCF binding.

I reported this vulnerability to Microsoft, and they have confirmed and accepted it as a defect but have not yet seen fit to patch it in .NET 3.0/3.5 (I haven’t yet looked at .NET 4.0 beta to see whether it may have been addressed there). I discussed this with some Redmond people at the PDC last year, and the view seemed to be that it wasn’t a significant vulnerability because there is in general no way for a user who isn’t an administrator to enumerate named pipe objects in order to discover service endpoints to attack. Personally, I think this is a short-sighted view, because it ignores the fact that anyone who knows the URL of the service’s named pipe endpoint can derive the name of the pipe, in exactly the same way as the WCF client-side channel stack does. If one knows the service endpoint URL, it is remarkably easy to create an exploit to appropriate the credentials of a client accessing the service, for example using the named pipe support provided in the .NET Framework 3.5 System.IO.Pipes namespace. With a bit of extra work, one can make the exploit masquerade as the service, spoofing response messages to the client. In future posts I will explore how this is done, not least because it illuminates a number of the factors which make the NetNamedPipeBinding difficult to interoperate with programs which do not use the .NET Framework.

Update: This defect is fixed (well, arguably only partly fixed) in .NET 4. The default DACL created when the service channel listener is opened now gives FILE_CREATE_PIPE_INSTANCE permission only to the logon session SID under which the service is running. The superfluous ACE in respect of the CREATOR OWNER SID has also been removed.

It’s arguably only a partial fix, because if the WCF service is hosted in a process running in an interactive logon session (e.g. self-hosted in a console application), any other process running in that logon session can still execute a squatting attack on the service, because it will still have FILE_CREATE_PIPE_INSTANCE permission on the named pipe created by the standard NetNamedPipeBinding.

Before that, I want to post a fuller workaround to the vulnerability, which I have added to my AclSecuredNamedPipeBinding class.

The strategy for the workaround is:

  • register an event handler which will fire when the ChannelListener has reached the Opened state. At this point, the listener will have just created the named pipe, complete with its defective DACL.
  • in the event handler, amend the DACL to remove the offensive grant of FILE_CREATE_PIPE_INSTANCE permission to all but the service owner.       

In principle, this does leave a race condition: there is a scintilla of time between the listener creating the pipe with the insecure DACL, and the DACL being patched by our event handler, which in theory an attacker could exploit. In practice, though, this will be very hard to do, since the publishing of the pipe name (a GUID) to shared memory also only happens fractionally earlier.

The first step is very easy to accomplish, with this change to the BuildChannelListener override:

public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
{
   IChannelListener<TChannel> listener = base.BuildChannelListener<TChannel>(context);
   PropertyInfo p = namedPipeDuplexChannelListenerType.GetProperty("AllowedUsers", BindingFlags.Instance|BindingFlags.NonPublic);
   p.SetValue(listener, _allowedUsers, null);
   listener.Opened += new EventHandler(listener_Opened);
   return listener;
}

private void listener_Opened(object sender, EventArgs e)
{
   ChannelListenerBase listener = sender as ChannelListenerBase;
   if (null != listener)
   {
      // code to patch the pipe DACL goes here
   }
}

The second step is a bit more of a challenge, but the types provided in the .NET 3.5 System.IO.Pipes namespace can be used to do most of the heavy lifting. A possible implementation is shown below:

/// <summary>
/// If a WCF NetNamedPipe service endpoint is listening at the specified URL, patches the DACL of the
/// named pipe to deny the FILE_CREATE_PIPE_INSTANCE permission to all but the pipe owner i.e. the identity
/// of the service process which opened the endpoint listener.
/// </summary>

/// <param name="serviceUrl">The URL of the WCF service endpoint</param>

public static void PatchPipeSecurity(Uri serviceUrl)
{
   string pipeName = ResolveServiceUrlToPipeName(serviceUrl);
   using (NamedPipeServerStream sstm = new NamedPipeServerStream(pipeName, PipeDirection.InOut, -1,
            
PipeTransmissionMode.Byte, PipeOptions.None, 0, 0, null, System.IO.HandleInheritability.None,
            
PipeAccessRights.ChangePermissions))
   {
      PipeSecurity pipeSecurity = sstm.GetAccessControl();
      foreach (PipeAccessRule pipeAccessRule in pipeSecurity.GetAccessRules(true, true, typeof(SecurityIdentifier)))
      {
         if (AccessControlType.Allow.Equals(pipeAccessRule.AccessControlType))
         {
            // Remove the CreatorOwner ACE - it is redundant

            if (new SecurityIdentifier(WellKnownSidType.CreatorOwnerSid, null).Equals(pipeAccessRule.IdentityReference))
            {
               pipeSecurity.RemoveAccessRuleSpecific(pipeAccessRule);
               continue;
            }
           
            // Remove the FILE_CREATE_PIPE_INSTANCE permission from each ACE except the one

            // for the service account itself, which will be the identity executing this code
 
            // Update: Because of the way the permissions defect has been fixed, this code is not sufficient
 
            // in .NET4, where we would need also to grant FILE_CREATE_PIPE_INSTANCE permission to the
 
            // service owner account. Without this the service will not work, and will hang in a busy loop.
 
            if (!WindowsIdentity.GetCurrent().User.Equals(pipeAccessRule.IdentityReference))
            {
               PipeAccessRights permissions = pipeAccessRule.PipeAccessRights;
               permissions &= ~PipeAccessRights.CreateNewInstance;
               PipeAccessRule newAccessRule 
                   =
new PipeAccessRule(pipeAccessRule.IdentityReference, permissions, AccessControlType.Allow);
               pipeSecurity.RemoveAccessRuleSpecific(pipeAccessRule);
               pipeSecurity.SetAccessRule(newAccessRule);
            }
         }
      }
      sstm.SetAccessControl(pipeSecurity);
   }
}

The listing above does not show the implementation of the function ResolveServiceUrlToPipeName. One way of implementing this in just a few lines of code is to use reflection to call the internal static method GetPipeName of the internal WCF type System.ServiceModel.Channels.PipeConnectionInitiator, which is the mechanism used by the WCF client-side channel stack when a WCF client connects to the service.

Type pipeConnectionInitiatorType =
  
Type.GetType("System.ServiceModel.Channels.PipeConnectionInitiator, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", true);
MethodInfo getPipeNameMethodInfo =
    pipeConnectionInitiatorType.GetMethod(
"GetPipeName", BindingFlags.NonPublic | BindingFlags.Static);
string pipename = (string)getPipeNameMethodInfo.Invoke(null, new object[] { new Uri("net.pipe://localhost/<service url here>") });
return pipename.Replace(@"\\.\pipe\", String.Empty);

In this context, such an implementation based on reflection is a reasonable approach, because it only needs to execute once when the service opens. With an eye to the possibility that I might want an implementation which would be called more frequently, and avoid the overhead of reflection, I have actually created my own code to do this, which I will cover in a separate post.       

Comments

 

j. monty said:

Thanks for this 4 part article on WCF and security. Have you had a chance to look into .NET 4.0 WCF? Have any of the items been fixed that you have discovered?

thanks!

August 12, 2010 5:42 PM
 

Luis said:

"In future posts I will explore how this is done, not least because it illuminates a number  ..."

When we will be able to see this future post? Any time soon ????

October 9, 2010 10:14 PM
 

Circa2010 said:

Nice articles Chris. Will it be possible for you to put up a sample download of your application/library?

December 9, 2010 9:24 PM

Leave a Comment

(required) 
(optional)
(required) 
Submit
Powered by Community Server (Commercial Edition), by Telligent Systems