This is the second in a series of articles that I'm writing about some of the cool new security features in Microsoft Windows Vista. The first article demonstrated how to add a new symmetric cipher algorithm to "Crypto API: Next Generation" (CNG) and the Cryptographic Message Syntax (CMS) APIs. Future articles and sample code will address the topics including IPsec, IPv6, the smart-card module minidriver interface, and fuzz testing (using malformed data input for testing).
The focus of this article is to demonstrate the recommended use of the new Microsoft Windows Firewall API. Specifically, I show how to program Windows Firewall from the perspective of an independent software vendor (ISV) that wants to configure its network-based application with an as-restrictive-as-possible firewall-rule set. A complete set of code samples accompanies this article, including a demonstration socket-based client/server program, and a firewall tool that shows how to add and remove firewall rules programmatically.
About the Windows Firewall API.
Existing Windows Firewall sample code.
Introduction to the Windows Filtering Platform, on which the Windows Firewall is based. I recommend reviewing the five or so (short) introductory Web pages that follow the article in the MSDN table of contents, including the architectural diagram that shows how the Windows Firewall (and a typical socket application) interacts with the filtering platform.
The second item of business to address before I get to the sample code is to explain briefly the difference between the Windows Filtering Platform and the Windows Firewall APIs.
My sample demonstrates the use of two closely related Windows Vista APIs: those for the Windows Filtering Platform and those for the Windows Firewall. The Windows Filtering Platform API is new in Windows Vista. While some of the Windows Firewall API functionality is available in Microsoft Windows XP, it has been greatly enhanced in Windows Vista. And, while some of the capabilities of the Windows Filtering Platform and Windows Firewall APIs overlap in Windows Vista, it's important to understand the scenarios to which each applies.
I learned this lesson the hard way; my first attempt to add firewall rules programmatically for my sample TCP server was based on the Windows Filtering Platform API. This was partly because I first found a sample in the latest SDK that demonstrates adding Windows Filtering Platform–based rules for MSN Messenger. Because I had no reason to suspect that this wouldn't be a good starting point for my own work, I used it as a baseline. Using the Windows Filtering Platform API instead of the Windows Firewall API was also initially attractive, because I found the Windows Filtering Platform API to be more intuitive.
Furthermore, my Windows Filtering Platform rules seemed to be ignored. If I clicked the Keep blocking button, the client could never connect, regardless of my own filter. If I clicked the Unblock button, the server was able to receive all traffic, regardless of the client address—for example, even though my filter was more restrictive. In other words, despite the presence of my filter, I was still observing the default per-application behavior of the Windows Firewall.
This was contrary to my goals for this demo, as I wanted to demonstrate the proper way to install very specific firewall rules and avoid the preceding potentially confusing dialog box.
Subsequent conversation with the engineering team at Microsoft clarified that the Windows Filtering Platform API is not intended to be used in the way in which I was using it. In scenarios in which the Windows Firewall is enabled, and a standard network-based application needs to open one or more ports, the Windows Firewall API should be used. In contrast, the primary purpose of the Windows Filtering Platform API is to facilitate development of rule-based network-infrastructure technologies, such as third-party firewalls. Indeed, the Windows Firewall is based on the Windows Filtering Platform API, as I'll demonstrate later, and non-Microsoft firewalls also are intended to be so.
We can infer from this situation that it's Microsoft's intent that the average network application should not interact with the Windows Filtering Platform API in any way. Instead, such applications should call into the higher-level Windows Firewall APIs during installation and un-installation. This is best practice, as the Windows Firewall is enabled by default. However, application developers should also provide documentation that details the expected firewall-rules configuration for proper and secure operation. This is important, as the Windows Firewall may have been replaced by a non-Microsoft solution (and, therefore, a different programmatic interface). Finally, this should serve as a gentle reminder that Windows should always have a software firewall enabled and correctly configured!
Let's consider a specific example in order to motivate the discussion of the Windows Firewall API. Suppose, at a large manufacturing enterprise, that one business group maintains an internal software application that stores customer information, including some sensitive data like credit card and social security numbers. Suppose, further, that to create synergy across the organization, another business group must now have access to the same data.
As the in-house developer in charge of this line-of-business (LOB) application, suppose that I've been asked to confirm that it's sufficiently robust for the new demands that will be placed on it, including the need for additional users and increased exposure of the sensitive data to the corporate network.
Administrative access to the server may be tightly controlled and audited.
The application architecture may be three-tier, with the data hosted off-box.
IPsec policies may be in place to protect network traffic, at least between the application and database servers.
These are all good things. However, additional defense-in-depth measures are recommended. To put it another way, as a software developer, I shouldn't assume that security mechanisms beyond my direct control, such as a hardware-based firewall, are present or configured correctly. This is where a discussion of the Windows Firewall becomes interesting. In the context of this example, the objective is to configure the software firewall under the assumption that it's the last defense standing between an army of bad guys and the sensitive customer data. Realistically, leveraging the security mechanisms that are offered by the underlying platform is especially important in light of the fact that many organizations lack the resources to deploy the additional security mechanisms mentioned earlier.
In summary, I know that two business groups will be accessing sensitive customer data from the corporate network via my application. Although the following discussion will focus only on configuring the software firewall, in reality, other aspects of the security design of the application need to be considered. For example, has the code recently undergone a thorough security review?
With these requirements in mind, let's take a look at some sample code.
In the example scenario that I described earlier, I'm a developer who's been asked to prepare an existing LOB application for broader use in a corporate network. In this article, my intent is to use this scenario to demonstrate the use of the new, improved version of the Windows Firewall API. It bears mentioning that this implies that the application is hosted on Windows Vista or a later version of Windows. Admittedly, this is more of a server scenario, and will be more relevant as soon as the next version of Windows Server (code name "Longhorn") is released. I'm not aware of any plans to port the new Windows Firewall API down-level.
A portion of the sample code that accompanies this article is based on a pre-existing sample from the Windows Vista version of the Windows SDK (formerly known as the Platform SDK). The original sample code for the TCP server application can be found in the "Samples\NetDs\winsock\securesocket" subdirectory of the SDK installation. The original code included some logic that pertained to IPsec and IPv6, which I've intentionally disabled or removed in order to limit the scope of this particular discussion. Future articles in this series will include a complete analysis of introducing those technologies into the context of a similar sample.
More Windows Firewall–related sample code also accompanies the latest Windows SDK. See the Samples\Security\WindowsFirewall subdirectory.
I mentioned in the beginning of this article that I was initially confused about the difference between the Windows Firewall API and the Windows Filtering Platform APIs. As soon as I understood how to access the firewall, I became quite impressed with its simplicity of use. A firewall rule includes configurable conditions for a variety of network-traffic characteristics (ports, addresses, and so on). A rule must specify an action: Allow or Block. Therefore, in order for an Allow rule to apply, each of its conditions must be met.
This is exactly the behavior that I want to apply in my sample code. Namely, the application is opening a socket on the server and waiting for clients to make requests. By default, the application is listening on a port that I expect to be blocked by the Windows Firewall. I want to cause the firewall to open that port, but to allow through only network traffic that meets certain conditions. I also want to make those conditions as restrictive as possible. Any traffic that doesn't meet every filter condition should never be able to access my application. This is an important security concept; the default answer for inbound packets that want to traverse the firewall, even when my application is listening on its port, is still "No!"
The protocol in use is TCP/IP. For example, we won't accept UDP traffic.
The destination port is the specific one upon which my application is listening.
The client address is of an expected range.
Given that I'm testing this example only with IPv4 addresses, this condition is easy to implement for small organizations, especially those that use NAT. In those cases, the client-address condition might stipulate 192.168.1.0 with the corresponding mask of 255.255.255.0.
For organizations with multiple noncontiguous address ranges, managing the configuration of this rule is a little tougher, but certainly not impossible.
Finally, as an added bonus, I'd like to ensure that the preceding rule applies only to my specific application, and only when it's running. That is, I don't want another process on the host server to be able to receive my traffic, intentionally or otherwise.
As I mentioned previously, a Windows Firewall API rule exposes a variety of optional conditions. Programmatically, the conditions are exposed as properties of the INetFwRule interface. In fact, the available set of properties is sufficiently expressive that the desired behavior of the sample socket application can be achieved with a single rule. That's exactly what I hoped for: Conceptually, I want to open a port, as well as enable a specific application to listen on it, but only to certain types of traffic. It's easy to imagine how a rules engine that requires two separate rules to implement that behavior might, in fact, not be able to achieve it. For example, the resulting filters could be overly permissive due to a port being open but not bound to a specific application. I'll show how to test for such situations in the next section.
In the meantime, here's the code that creates the rule and enables it on the host. The function is WFAddRule, and it is part of my FwTool.exe utility. I'll discuss this utility in more detail in the Testing section.
There are a few things to note about the preceding sample code. Firstly, for the sake of brevity, I've omitted the definition of two dependencies of this function. One is the error-checking macro CHECK_COM2, which simply prints a debug string and throws an exception if the wrapped COM call fails. The other is the helper routine _CoCreateInstanceAsAdmin, which is essentially the same as the routine that is documented in the SDK in the COM Elevation Moniker section (see The COM Elevation Moniker in the MSDN Library).
The next thing to note about the code is that, although I'm using the Windows Firewall COM classes via Visual C++, I'm avoiding some of the usual interoperability nastiness (for example, explicitly converting LPWSTRs to BSTRs) by using the "netfw.tlb" interop library that is included with the SDK.
With those general comments in mind, I'll run through the main purpose of the routine: adding the firewall rule for my demo server application. Table 1 lists an explanation of each property, in turn.
A diagnostic field, visible programmatically and via the Windows Firewall with Advanced Security MMC snap-in.
Take another look at the Windows Security Alert dialog-box screen shot, in the previous section. Notice that the default firewall behavior is to use the program binary, without the extension (.exe), as its rule name. I'll discuss this behavior in more detail later. In the meantime, the caller of my WFAddRule function is expected to provide that string (in this case, "stcpserver") as the first parameter.
This property should be set to the full path of the server-application binary. By using this property, I'm preventing a binary at any other location in the file system from listening on my port.
See the NET_FW_IP_PROTOCOL enumerated type in the public icftypes.h header. Defined values correspond to TCP, UDP, and ANY. This example uses this property to restrict allowed traffic to TCP.
Specifies the port upon which my application is allowed to listen. For handling more complex scenarios, a comma-delimited list of ports may be used instead (see LocalPorts Property of INetFwRule in the MSDN Library).
During testing, I'm typically using this property to specify a single address on my test LAN, in order to ensure that the test client is allowed or blocked as appropriate. For deployment, more sophisticated use of this property will be required; it can take a variety of interesting values (see RemoteAddresses Property of INetFwRule in the MSDN Library).
This property should be used to specify a common group name for all rules that apply to a given application or feature. This demonstration requires only a single rule, so that the value of this property is diminished. However, its presence allows an administrator to enable, disable, and remove rules by using group name, instead of having to do this for each individually. The syntax of this property allows the value to be retrieved by using a resource table, which facilitates localization. The sample code that accompanies this article demonstrates this "indirect string" configuration. A difficult-to-diagnose error (INetFwRules.Add returns 0x80004005) occurs if the indirect string is incorrectly configured; keep in mind that it must be loaded from system32.
This property indicates the policy profile(s) in which the rule should apply. For the list of possible values, see the NET_FW_PROFILE_TYPE2 enumerated type in icftypes.h. Note the behavior of my test program, which installs this rule; it specifies that the new rule should apply in whichever profiles are in effect at the time of installation. However, if the server needs to be able to run under a different profile (for example, Domain versus Public), this logic will need to be modified accordingly.
This property should be simple to understand: A given rule either blocks traffic or allows it. This example does the latter.
Another straightforward property: A rule can be present, but disabled. However, I want my rule to be enabled immediately; therefore, this property is set to True.
In summary, creating and applying the rule is not a complex task. As soon as the rule is in effect, however, it needs to be tested. This presents a more interesting challenge, and it is the topic of the next section.
In other words, the first option allows the firewall rule (as discussed in the previous section) to be created based on the specified server-application name, server port, and allowed client-address range. The second option allows the rule to be deleted. The third option displays the resulting Windows Filtering Platform filters that are created by the firewall under the covers.
Run stcpserver.exe on the server-side host machine, as shown in the following command. Because its rule has already been added to the firewall, you shouldn't see a Windows Security Alert dialog box.
Run stcpclient on the client-side host machine, as shown in the following command. The server DNS name must be specified on the command-line. Because this client is the one that is allowed by the installed firewall rule, it immediately connects, exchanges some data with the server, and disconnects.
Here's the additional output that is displayed by the server in response to the client-connect and data-exchange. Then, the server re-listens on its socket.
0000  5c 00 64 00 65 00 76 00 69 00 63 00 65 00 5c 00  \.d.e.v.i.c.e.\.
0010  68 00 61 00 72 00 64 00 64 00 69 00 73 00 6b 00  h.a.r.d.d.i.s.k.
0020  76 00 6f 00 6c 00 75 00 6d 00 65 00 34 00 5c 00  v.o.l.u.m.e.4.\.
0030  74 00 65 00 73 00 74 00 5c 00 73 00 74 00 63 00  t.e.s.t.\.s.t.c.
Windows Vista includes a built-in firewall-management console, which I found (along with FwTool.exe) to be a useful way to check my work as I was developing and testing the rule code that is discussed in the previous section. To access the console, click Start, type WF.msc into the Start Search box, and then press Enter. I started by selecting Inbound Rules in the left pane. It's educational to examine the rules that are present by default.
In the center pane of the preceding screen shot, all of the expected properties of the custom rule are present (including a number of fields that I'm not showing, as the image would have been too wide). For example, the host machine is a member of a test (Active Directory) domain, and was on that network (that is, as opposed to a roaming laptop that is connected to public Wi-Fi) at the time that the rule was added. Therefore, the rule is applied in the Domain profile. Also, note that I configured a single test client to be allowed through the filter—namely, the client with IPv4 address 192.168.1.192.
No previous firewall rule exists for an application.
That application attempts to open a network port.
The Windows Security Alert dialog box appears (see the preceding sample screen shot).
The user clicks the Unblock button (or the Keep blocking button, which results in a different set of default rules).
I recommend using the sample application to cause the default Unblock rules to be created and to use the firewall MMC snap-in to review them. Not surprisingly, when this happens, all network traffic on any port and from any client can now be received by the designated application. This is perhaps a reasonable default behavior (based on the assumption that the user recognizes the application in question as trustworthy), given the need to compromise between usability and security in a consumer operating system. However, it further reinforces why the application developer should implement a much tighter rule configuration for installation (and for un-installation).
As soon as I had the stcpserver.exe application rule configured, I wanted to test each property of it, to ensure that I was demonstrating a truly locked-down configuration. With that said, I realized that some of the properties would be easier to test than others. Furthermore, I assumed that there would be some properties that I'd like to test on an as-needed basis, and that testing these would require modifying my socket-related code on the client, as well as on the server. However, I didn't really want to expose this in the command-line options (for example, the ability to send and receive UDP traffic).
To avoid that extra work, I used an existing network-testing tool called NetCat. The current Windows version can be downloaded by clicking here.
First, as a warm-up for the testing phase of preparing this demo, I wanted to ensure that, based on my new firewall rule, only the designated server-application name is allowed to listen on the open port. I started by configuring the server rule as discussed earlier.
Next, I ran NetCat (nc.exe) on the same server, with the appropriate command-line options to listen on the same port (type nc.exe –h for command-line help).
As a result, I immediately see the Windows Security Alert dialog box.
This is good; it indicates that, even though there are conditional "unblock" rules associated with the server-application port, only binaries of the same name can open it. However, it's important to mention that even a totally different application, if renamed to "stcpserver.exe," can indeed open the port. I confirmed this by renaming "nc.exe" to "stcpserver.exe" and then rerunning it with the same command-line options, as shown previously.
Next, I ran the client application (shown in the following code block) from an allowed machine. In response, I saw "12345678" displayed by stcpserver/nc.exe on the server, indicating that the connection and data transmission were successful.
Therefore, it is indeed possible to bend the firewall rules a little more than you might like! Of course, if an attacker is actually able to run code in this manner on a server, there may be broader security problems to worry about. Still, I hope that Microsoft adds strong application identity to the firewall in a future release, so that only a binary with a given cryptographic hash can match a given rule.
One final note about testing: I wanted to be sure to remove any firewall rules that were created by each test scenario, in order to ensure that downstream test results wouldn't be affected. This can be accomplished via my FwTool.exe tool (shown in the following code) or the firewall MMC snap-in (see previous). Also, after running this particular test case, don't forget to rename nc.exe and stcpserver.exe, as appropriate!
In addition to verifying the behavior of the application-identity filter that I added, I wanted to confirm that the protocol filter was working as expected. In particular, only TCP traffic should be traversing the firewall, based on my rule: no UDP traffic. This is easily confirmed by using a variation of the test configuration that is discussed in the previous section.
First, I added a rule to allow NetCat the same network access that I granted the stcpserver demo application.
During any testing effort, I recommend running a "positive" test case in conjunction with every "negative" test case. In other words, try the variation that should work, and then change a single variable to try the variation that shouldn't work. This pattern is especially important when testing something as complex—and as critically important—as network security. To accomplish this in my current test scenario, I ran NetCat in TCP listen mode (its default) on the server as a positive test case.
I confirmed that the text that I entered at the client was not echoed on the server. Finally, I cleaned up the created rules on the server to prepare for the next phase of testing.
Verifying the client-address component of my demo rule is also straightforward. The optimal test scenario for the client-address filter requires three machines: a server, an allowed client, and a denied client. However, there are also a variety of permutations that don't require a second client.
The first step is to set up the server rule and confirm that the allowed client can successfully connect, as discussed previously at the beginning of the Testing section. Then, attempt to connect to the server from a different client (that is, a different IP address).
The preceding error code corresponds to "Connection timed out" (see Windows Sockets Error Codes in the MSDN Library). This confirms the negative test case, as expected: A disallowed client address cannot connect to the server.
In general, the proper configuration and use of a software-based firewall is an important defense-in-depth measure. With the new Windows Firewall API, the Windows Vista release has made proper programmatic firewall configuration easier to accomplish. This is particularly true for conscientious ISVs that want to make their customers' lives easier by providing built-in firewall-rule configuration with their network applications. The demo socket-based application that is discussed in this article is an example of this approach.
I am indebted to David Abzarian, Mahesh Mohan, Philippe Joubert, and Sarah Wahlert at Microsoft for their technical assistance in preparing this article.