Last weekend at Shmoocon, I demonstrated how an attacker can trick certain WCF web services into performing an unauthorized port scan of machines behind a firewall. For those that were not able to attend the talk, the slides are posted here. The part that covers the port scanning technique may not be clear in isolation, so I’ll try and explain it in detail. The problem is related to the WSDualHttpBinding, so in order to understand how the scanning technique works you must first understand some WSDualHttpBinding basics.
The WSDualHttpBinding is one of several “Duplex” WCF bindings. The term Duplex refers to the bi-directional nature of the communication channel, meaning that both the client and the service can directly send messages to each other. This is ideal for scenarios where a service needs to “push” data down to a client, rather than the alternative of constantly polling the server for a callback. In order to do this over HTTP, which is by nature a one-way protocol, WCF sets up a dedicated HTTP listener port on the client that accepts incoming HTTP requests from the service (known as the callback channel). If you are like me, you probably just raised an eyebrow when I said that WCF sets up an inbound HTTP listener on the client machine. This scenario sounds odd from a security perspective, which is what initially caught my eye.
The first step in establishing a session with WSDualHttpBinding requires the client and server to negotiate the duplex connection. This negotiation is a required part of the connection sequence, and is the mechanism that can be abused to perform remote port scanning. The negotiation starts with the client sending a “CreateSequence” SOAP request to the web service endpoint. A typical CreateSequence request is shown below.
As you can see, the CreateSequence request includes a “ReplyTo” address. This address is the URL of the callback channel at which the client expects to receive callback requests from the service. When the service receives this request, it reacts by initiating a “CreateSequenceResponse” to the ReplyTo address, and then responding to the original request with a “202 Accepted”. Conceptually this is represented by the diagram below. Note that the circled numbers represent the order in which each request and response occurs.
The scenario above represents the intended chain of events for a CreateSequence negotiation. There are a few important things to note:
- There are two separate HTTP conversations occurring. One is between the client and the service over port 80, and the other is between the service and the client on port 8000.
- When the service receives a CreateSequence request, it will immediately attempt to issue the CreateSequenceResponse request to the address that is passed within the ReplyTo value. This does NOT have to be the same address (or port) where the CreateSequence request originated from.
Next, let’s introduce another slightly more complex example. In this scenario, we have 4 machines:
- The client, which in this case will end up being the bad guy
- The WCF service that uses WSDualHttpBinding
- Two unrelated hosts that will serve as targets
The client in this case will send two CreateSequence requests to the service. The first request will include a ReplyTo address of Target1, and the second request will include a ReplyTo address of Target2. Again, the circled numbers represent the order in which each request and response occurs.
This diagram is much more interesting as it depicts what is certainly NOT an intended use case. As illustrated above, the first CreateSequence request (1) causes the service to initiate a connection to Target1 on port 8000, just as the second CreateSequence request (4) does to Target2. Even more interesting is that the “Accepted” HTTP response (7) to the second CreateSequence request (4) does not occur until AFTER the connection to Target1 times out (5). This means that the delay between the second CreateSequence (4) and the subsequent “Accepted” response (7) was directly related to the response time of the first CreateSequenceResponse attempt (5). It appears that a WCF service will not respond to a new CreateService request until all previous CreateSequenceResponse requests have either been acknowledged or timed out.
What Does this Mean?
Based on the behavior described above, the CreateSequence HTTP response delay is an effective mechanism to determine the state of a prior connection request. By issuing multiple requests to different hosts and ports, we can use this behavior to probe remote hosts from the server hosting the WCF service. Depending on the connectivity available from the host, we can even probe systems that would not otherwise be available to us (such as on an internal network or DMZ).
In order to prove this theory, I wrote a utility to issue successive CreateSequence requests to a WCF service that each have a different ReplyTo address and/or port. It measures the time between a CreateSequence request and the “202 Accepted” response in an attempt to determine whether a previous request was successful. The utility is fairly simple and operates as follows (assume that Service is the WCF service we want to mis-use, and that the Target is the machine we want to port scan):
- Request #1: Issue a CreateSequence request to Service which will ReplyTo Target on Port 1. The delay (if any) on this first request is not associated with a connection we initiated so the timing of this first response is ignored.
- Request #2: Issue another CreateSequence request to Service which will ReplyTo Target on Port2. A timer is used to measure the time between this request the “202 Accepted” response from Service. This response will not occur until the previous CreateSequenceResponse has been acknowledged or timed out. As such, this delay will be used to infer the outcome of the probe caused by Request #1.
- Request #3: Issue a CreateSequence request to Service which will ReplyTo Target on Port3. A timer is used to measure the time between this request the “202 Accepted” response from Service. This response will not occur until the previous CreateSequenceResponse has been acknowledged or timed out. As such, this delay will be used to infer the outcome of the probe caused by Request #2.
- and on and on and on…
Proof of Concept
As a proof of concept, I deployed an instance of the MSDN CalculatorDuplex sample service to a virtual machine in the Microsoft Azure cloud to use as a test case. This service is a simple calculator web service that uses the WCF WSDualHttpBinding. As it turns out, the Azure environment was a great place to test this concept since Azure VMs actually reside on an internal private 10.x.x.x network behind a firewall. Conceptually, this is represented in the diagram below (note, this is an over simplified diagram based on what I have seen in my limited testing with Azure).
Based on an analysis of the VM running the sample service, it also appeared that the VMs within the Azure environment typically run IIS on port 20000. I used the utility to remotely scan other VMs within the 10.x.x.x address space on this port through requests to the Calculator service. The results from the initial test are shown in the screenshot below.
As you can see, the result of each probe is inferred based on the average response time of the other requests. The scan above shows that four of the probes returned very quickly (around 114 ms) while the others appear to have timed out. The probes that do not time out in this case are the other internal VMs that are up and running IIS on port 20000. As a second test, I used the utility to probe ports on the localhost of the machine running the Calculator service. As you can see below, the probe to port 3389 times out while the others return after about 1 second. So in this case, the Remote Desktop service is running on the localhost.
So to summarize, this appears to be a potential design flaw within the WCF create sequence negotiation process. As a result, any service that uses this binding can be abused by a remote user to scan other hosts (even those behind a firewall that they may not otherwise have access to). Certain web-based attacks can also be proxied through these services since the remote attacker has the ability to control not only the target address and port, but also the complete URI that will be requested. The source code for the scanner utility is posted here for reference.