Windows Communication Foundation (WCF) is a framework for building service-oriented applications in the .NET Framework.
A trend that we’ve noticed in .NET services is the exposure of dangerous methods through insecure WCF endpoints. Additionally, most of these services are started automatically as LocalSystem, which is the highest user privilege level available. This results in a situation where a WCF endpoint may become a gateway for normal users to abuse privileged service methods. We’ll be sharing the techniques that we’ve used to identify insecure WCF endpoints as well as details regarding endpoint exploitation using a custom WCF client.
Prior to discussing analysis techniques, it’s best that we provide a brief explanation of the underlying architecture of WCF. The “ABC” paradigm is a theme commonly used to explain the WCF framework. Addresses, Bindings, and Contracts are the three core components of a WCF Endpoint.
A – The address of an endpoint is defined by the EndpointAddress class.
An EndpointAddress instance includes a unique URI used to represent the address of a WCF service. Endpoint address properties may either be defined within a service’s configuration file, metadata, or source code.
B – WCF Bindings are used to specify an endpoint’s communication details. Bindings define the accepted transport protocol and encoding scheme, and can also be used to configure transport security. WCF includes a few pre-defined binding classes such as BasicHttpBinding, NetTcpBinding, and NetNamedPipeBinding. NetNamedPipeBinding is a very common binding choice among Windows services built using the .NET Framework.
C – Service contracts specify the operations exposed by an endpoint. Service contracts are created by defining an interface. Each method within the interface corresponds to an operation contract. Service contracts are identifiable by the ServiceContractAttribute attribute, and operation contracts by the OperationContractAttribute attribute.
The “ABC” paradigm is used to simplify WCF service creation into a 3 step process:
1. Define and implement a service contract.
2. Choose or define a WCF binding.
3. Bind the service to an endpoint address.
This high-level understanding of WCF will provide us with a bit of clarity as we begin to analyze the code of a WCF service.
Now, lets talk about ways we might enumerate relevant targets for analysis. Our ideal target is an active Windows service running as LocalSystem. The target service should also be a .NET assembly for it to use WCF. This excludes services hosted by “svchost.exe”, which often accounts for a majority of running services. We can perform a WMI query using “Wmic.exe” to help enumerate the services that meet these requirements.
In order to filter our list of services further, we should identify only the services built using the .NET Framework. Every .NET assembly includes the dependency “mscoree.dll”. Given this knowledge, we wrote a Python script using the “pefile” module that filters out services without this dependency.
As you can see, this leaves us with the service named VulnWCFService, which will be the focus of this blog post.
VulnWCFService is a very simple WCF service that we wrote to demonstrate the analysis and exploitation of an insecure endpoint. Its implementation is modeled after several WCF services that we’ve come across in our research. The trivial vulnerability introduced into the operation contracted is based on a real vulnerability that has since been reported to the vendor. We’ll begin our analysis by decompiling VulnWCFService using a tool called “dnSpy”. dnSpy is an open-source debugger and .NET assembly editor that leverages the “ILSpy” decompiler engine.
First, we open the service assembly with dnSpy, and click the arrow to the left of the VulnWCFService namespace. This will reveal the namespace components.
Microsoft’s Interface Naming Guidelines dictates that interface names are prefixed by the letter “I”. As service contracts are defined by interfaces, IVulnService is most likely the service contract definition that we are interested in reviewing.
Notice the bracketed ServiceContract and OperationContract attributes. This confirms that the IVulnService interface is in fact a service contract. The operation exposed by this contract is defined by the method named “RunMe” which accepts a single string parameter.
To review the implementation of this method, right-click on the method name in the source code window and select the “Analyze” option. Then, expand the “Implemented By” tree in the “Analyzer” window at the bottom of the screen, and double-click the method shown in order to view its decompiled source code.
As we can see in the code, this method simply appends a user-supplied string to a command line argument for “CMD.exe”. This implies that a WCF client would be capable of directly influencing the execution of a privileged process. Contrived as this example may seem, similarly vulnerable code paths have proven to be frighteningly common in many applications.
In order to access the dangerous operation that has been exposed, we must determine how to properly connect to the service endpoint. Let’s review the decompiled source of the VulnWCFService class.
The first thing to notice is that the VulnWCFService is using the System.ServiceModel namespace. The System.ServiceModel namespace and sub-namespaces contain the types necessary to build WCF service and client applications.
Within the OnStart function, we are introduced to the baseAddress variable. baseAddress is a URI instance that represents the address of our WCF endpoint. Endpoint URI’s may be broken down into 4 parts:
The scheme used by VulnWCFService is “net.pipe”, which is associated with the NetNamedPipeBinding binding. Our machine name is localhost, and no port number is provided. Lastly, the path for our endpoint is “/vulnservice/runme”. You’ll often find that an endpoint’s path name is indicative of the service and operations exposed. Note that in our example, baseAddress is clearly defined by a URI string. However, some real-world applications may retrieve the endpoint address from a configuration file, and others may implement dynamic address computation.
After defining an address, the ServiceHost is instantiated. A ServiceHost is responsible for the deployment and configuration of a WCF service. The ServiceHost constructor used by VulnWCFService returns an instance based on the service type and base address URI provided. The service type provided is derived from the type of the service’s contract. Soon we’ll see how ServiceHost methods are used to directly control the service.
As mentioned before, NetNamedPipeBinding is a very popular binding among Windows services. This binding informs us that service communications will be facilitated across named pipes. A NetNamedPipe is a WCF abstraction of native named pipes. My curiosity led me to investigate how exactly the exact relationship between native named pipes and NetNamedPipes.
The NetNamedPipeBinding object defines a NamedPipeTransportBindingElement private binding element named namedPipe. NamedPipeTransportBindingElement creates either a NamedPipeReplyChannelListener or NamedPipeDuplexChannelListener channel listener object, depending on the type of transport defined by the service contract. Channel listeners are responsible for creating channels and receiving messages Both of these classes are implementations of NamedPipeChannelListener, which leads to CreateTransportManagerRegistration when opened. This triggers the creation of a connection listener, which is a low level abstraction for listening for sockets/pipes. NetNamedPipe objects are used as connection listeners for NetNamedPipes.
To create a native named pipe, PipeConnectionListener calls the unsafe CreatePipe() method, which in turn calls the native CreateNamedPipe() method. This return’s a PipeHandle object to be used by the PipeConnectionListener. The name of the pipe created is a randomly generated GUID that is equivalent to the PipeName member of a PipeSharedMemory instance. The PipeSharedMemory instance contains the SafeFileMappingHandle that links the endpoint URI and native named pipe together. This call chain is simplified in the diagram above.
The call on line 37 to this.serviceHost.AddServiceEndpoint is used to prepare the WCF endpoint for deployment. It references the endpoint address URI, endpoint binding, and service contract previously defined to properly configure the service endpoint. This method call brings together the “ABC”s of WCF. Identifying the call to this method is especially useful when analyzing the obfuscated source code of a WCF service, as it may divulge everything that is needed to build a compatible client.
After the service endpoint has been added to the ServiceHost , calling the “Open” method will make the endpoint available for communication. At this point, a client may access the service using the endpoint URI. When the service is stopped, the “Close” method is called, decommissioning the service endpoint.
Now, that we understand the design of VulnWCFService, we can begin to build our client: “EvilWCFClient”. A WCF client or Client Proxy is an application that is capable of accessing a WCF service. It’s often the case that when a service is accompanied by a client application, there exists a shared assembly used by the client to facilitate WCF communications. If so, it may be possible to reference this proxy library from within Visual Studio in order to easily communicate with the associated WCF service. This would significantly reduce the amount of time required to build a client application.
It is also worth noting that a WCF client may be created automatically using the ServiceModel Metadata Utility Tool (Svcutil.exe) by pointing it at an active service that publishes metadata. However, metadata is not enabled by default and must be enabled using the ServiceMetadataBehavior behavior in the service.
Even if there exists no proxy library or WCF metadata, it remains possible to build a WCF client using the information disclosed through static analysis. We’ll demonstrate this starting with a new C# .NET Console App project in Visual Studio.
As mentioned before, System.ServiceModel is necessary for building client applications, so we must add a reference to this namespace. Following this, we must implement a service contract identical to that of the target service. We can do this by copying the service contract definition found in our decompiled source code over to our project’s namespace.
In VulnWCFService, AddServiceEndpoint reveals the following “ABC” information about the endpoint:
A service’s “ABC”s are key to establishing an endpoint connection. We’ll leverage channel-level programming to make use of these endpoint details and establish a connection to the service.
Channels provide a low-level programming model for sending and receiving messages. The WCF channel stack acts as a sort of pipeline for WCF messages sent and received as Message objects. A Channel Factory may be used to create and manage new channels for service communication. We’ll create an instance of the ChannelFactory class using our binding and address string to it’s constructor. The service contract interface name, IVulnService, will populate the generic type parameter in our instantiation.
After creating a channel factory, we follow up with a call to the CreateChannel method. This will return a ServiceChannelProxy instance, which may be used to call the remote operations defined within the service contract. ServiceChannelProxy uses .NET reflection in order to prepare a proxy implementation of the targeted service contract. Now that we have our ServiceChannelProxy, we’ll can simply call the proxy instance’s “RunMe” method in order to access the services privileged endpoint operation.
It is worth noting that this is one of the simplest examples of communicating to a WCF service. Certain service contracts may require the use of a DuplexChannelFactory for two way communication. ClientBase may also be used to communicate to WCF services.
After building the project and launching the application, we can see that VulnWCFService contains a new child process named “calc.exe”.
Despite the fact that our client was launched from a low-privilege context, the process created by the endpoint operation is executed by NT AUTHORITY\SYSTEM. This trait is the pitfall of insecure WCF services.
Although this example presents a rather trivial path to exploitation, it is worth hunting for less direct paths within more complex WCF services. For instance, we identified a service that allows a user to manipulate the Windows registry. This could be abused to modify the “ImagePath” subkey of a service and achieve privilege escalation.
Because there is limited prior research regarding this topic, the scope of impact for insecure WCF endpoints is unclear. I advocate that users make an effort to understand the attack surface of their devices. We’ve identified the exposure of WCF endpoints in several OEM services as well as several third-party applications.
Thankfully, some developers have demonstrated an ability to configure fairly secure WCF endpoints. The primary defense against WCF abuse is to restrict the capabilities of any exposed methods. Privileged methods should not offer excessive operational control to unprivileged users. We’ve also seen the use of token based authentication schemes which help to prevent arbitrary clients from communicating with a service.
During an audit of several Windows VPN services, we identified several WCF endpoints that offered direct control of command line parameters used in the creation of an elevated process. This allowed for local privilege escalation to the SYSTEM user.
Details regarding the vulnerabilities discovered during this audit may be found in VerSprite’s advisories repository
We plan to further investigate the state of WCF endpoint security in an effort to better understand the scope of this attack surface. We are also working to understand more about the low-level implementation of WCF communications, so stay tuned.
The source code for VulnWCFService may be found on VerSprite’s research repository via GitHub. Feel free to reach out with any questions you may have regarding our research.
VerSprite's Research and Development division (a.k.a VS-Labs) is comprised of individuals who are passionate about diving into the internals of various technologies.Our clients rely on VerSprite's unique offerings of zero-day vulnerability research and exploit development to protect their assets from various threat actors. From advanced technical security training to our research for hire B.O.S.S offering, we help organizations solve their most complex technical challenges. VerSprite's Research as a Service →