Windows Firewall with Advanced Security


The new firewall available with Windows Vista has more features than the previous version, and it is relatively easy to write code to configure the firewall properly for your application. Our sample code shows you how to check the current firewall settings, get and examine the current rules, create rules, and manipulate groups of rules. As with some of our other samples, we have yet more lessons from the School of Hard Knocks that will hopefully save you some time. Let’s dive in and take a look at what’s available and how to use it.

Global Firewall Settings

The first piece of code we’ll need retrieves a NetFwPolicy2 object, defined in NetFw.h, which in turn allows us to retrieve global settings, manipulate groups of rules, and obtain an interface to the rules. If you were thinking of getting the NetFwPolicy2 object from the NetFwMgr, like you would have for the older NetFwPolicy interfaces, think again: you just instantiate an instance with CoCreateInstance:

 hr = CoCreateInstance( __uuidof(NetFwPolicy2),                        NULL,                        CLSCTX_INPROC_SERVER,                        IID_INetFwPolicy2,                        (void**)&pFwPolicy2 );

A helpful hint is to search for CLSID in the NetFw.h header; this shows you the COM classes you can instantiate directly with CoCreateInstance, as opposed to having to acquire an interface pointer through a class method. The currently defined classes are:

 NetFwRule (Advanced Firewall) NetFwOpenPort NetFwAuthorizedApplication NetFwPolicy2 (Advanced Firewall) NetFwMgr

Now that you have an instance of the NetFwPolicy2 object, we can get information about the current status of the firewall, and which policy is in effect with the following:

 long CurrentProfileType; printf( "Checking current profile type:\n" ); hr = pFwPolicy2->get_CurrentProfileTypes( &CurrentProfileType ); if( SUCCEEDED( hr ) ) {    // The profile type can currently be treated as exclusive,    // but this may not be true in the future, so check it like this:    if( CurrentProfileType & NET_FW_PROFILE2_DOMAIN )      printf( "Current profile type is domain\n" );    if( CurrentProfileType & NET_FW_PROFILE2_PRIVATE )      printf( "Current profile type is private\n" );    if( CurrentProfileType & NET_FW_PROFILE2_PRIVATE )      printf( "Current profile type is public\n" ); }

If the system is joined to a domain and is able to locate a domain controller for its forest, the domain policy will take effect–which could be pushed down from the domain controller. If the system is exposed to a public network, the public policy would be used, and if you’re running on a private network, the private policy would take effect. When you write rules for your application, which policy to enable is a primary consideration; for example, we might not want something like Universal Plug and Play (UPnP) running on a corporate network–it’s unlikely to be a great idea on a public network, but it’s probably very useful on a private network. Think through your choices about which profiles to support with your rules in terms of principle of least privilege. If you use our GetFirewallSettings sample to dump all of the rules on your Windows Vista system, you’ll see some applications that have different rules for each profile and some that only support some of the profiles. You can also check whether modifications can succeed with this code:

 printf( "Checking whether modifications will succeed:\n" ); NET_FW_MODIFY_STATE ModifyState; hr = pFwPolicy2->get_LocalPolicyModifyState( &ModifyState ); if( SUCCEEDED( hr ) ) {     switch( CurrentProfileType )     {     case NET_FW_MODIFY_STATE_OK:       printf( "Modifications are OK\n" );       break;     case NET_FW_MODIFY_STATE_GP_OVERRIDE:       printf( "Firewall state set by group policy\n" );       break;     case NET_FW_MODIFY_STATE_INBOUND_BLOCKED:       // This means your rule will be applied, but       // won't work because of blocked inbound connections.       printf( "Inbound connections are blocked\n" );       break;     } }

For each policy level, the following properties are configurable on an individual basis:

  • BlockAllInboundTraffic

  • DefaultInboundAction–The shipping default is to block, and we’d strongly recommend not tampering with this setting.

  • DefaultOutboundAction–The default here is to allow connections to external systems.

  • ExcludedInterfaces–Lists any network interfaces excluded from this policy level.

  • FirewallEnabled

  • NotificationsDisabled–Disables desktop notification, which would typically be used in a controlled enterprise scenario.

  • UnicastResponsesToMulticastBroadcastDisabled–Allows or disallows responses directly to the sender of a multicast broadcast packet.

In general, applications should not modify systemwide settings, but it can often be useful to determine whether a setting might be in place that would cause your application to either malfunction or have reduced functionality and then notify the user. If it is important to the security of your application for the firewall rules to be in effect, you may want to warn the user if the firewall is disabled. Note that applications need to be fully elevated to administrator to change firewall settings.

Creating Rules

There are two main tasks to creating good firewall rules. The first is understanding how to use the APIs to create rules and put them into groups. Due to some documentation errors, this isn’t as easy as it looks, but we’ve sorted out these issues so you can avoid them. In all likelihood, unless you are one of the first ones to buy this book, the documentation errors will have already been fixed, and a couple of functional issues are scheduled to be fixed in Windows Vista service pack 1.

The second part of creating good firewall rules is much harder–knowing both how your application needs to work with the network and knowing the types of attacks that someone might launch against your application. First, we’ll show you what can be done with the Advanced Windows Firewall, and then we’ll show you the rules we wrote for our sample application and why each of these rules is important. One note, although the Advanced Windows Firewall is indeed very advanced compared to the previous Windows Firewall shipping in Windows XP, it isn’t going to allow you to do some of the things you could do with a fully featured, enterprise-class firewall. For the most part, these limitations aren’t a significant problem, but if they are, you can do nearly anything you like by writing filters for the Windows Filtering Platform, which is also documented in the Windows SDK. To be completely fair, it’s also true that the Advanced Windows Firewall allows you to create rules that might be difficult using other approaches, such as applying rules only to wireless interfaces.

Using the Advanced Windows Firewall rules, a firewall rule can have the following properties:

  • ApplicationName–This property takes a full path to the binary you’re running. We’d suggest copying the executables into a staging directory or creating two sets of rules, one for debug and another for release.

  • Description–The description is just a user aid and documents what you’re trying to accomplish with this rule.

  • Direction–This is the direction traffic will flow, and is one of two enum values: NET_FW_RULE_DIR_IN or NET_FW_RULE_DIR_OUT. Rules regulating what can go from your application to other points on the network are outbound rules, and rules regulating what can come from the network to your application are inbound rules. Note that some protocols, such as TCP, are considered directional, and enterprise-class firewalls can write rules that allow connections to be made in only one direction. This version of the firewall rules doesn’t support this level of functionality, although it isn’t as important for a host-based application-level firewall to be able to regulate the TCP connections at this level.

  • EdgeTraversal–This setting determines whether the traffic is allowed to be sent across the Teredo tunnel adapter. Unless you have an IPv6-enabled application and would like it to work beyond your local network, you can leave this at the default, which is disabled.

  • Enabled–Very simply, this determines whether the rule is turned on or off.

  • Grouping–The grouping property is important but tricky. If your rules have a grouping property, you can manage them all at once, which we’ll show you later in the chapter. The documentation tends to expect that you can feed a simple BSTR into this property, such as “My App’s Rule Group,” and it ought to work, but unfortunately, this won’t work until service pack 1. You can give it the path and string ID to a string stored in a resource DLL, and we’ll show you how this is done.

  • IcmpTypesAndCodes–The ICMP types and codes allows you to regulate what type of ICMP traffic is allowed to be passed along to your application. The types and codes are passed in as strings, so if your application sent pings (ICMP echo request) and expected replies, your rule would need to allow “0:0,8:0”–where type 8 is an IPv4 echo request, code 0 is the only documented code for that type, and type 0 is an echo reply type, again with a corresponding code of 0. If your application needed to deal with all of the ICMP unreachable packets, the way to write the rule is to specify “3:*”–which is interpreted as type 3, all codes. If you use the sample code provided, you can dump the rules that come with Windows Vista and see more examples. Hopefully, the details of how to actually use this property will be documented in the Windows SDK soon.

  • Interfaces–This specifies which interfaces the rule applies to. The default is all interfaces. We’ll present a sample showing how to do this shortly.

  • InterfaceTypes–This is a string specifying the type of interface to apply the rules on. Currently allowed values are RemoteAccess, Wireless, Lan, and All.

  • LocalAddresses–This specifies the local addresses a rule applies to. In most cases, you’d set this to “*”–meaning all interfaces. You can specify a list of comma-separated IP addresses (no spaces), a range of IP addresses (for example, 192.168.0.1-192.168.0.255), or you can use CIDR notation (192.168.0.0/24 in CIDR means the same thing as the previous range example). The documentation states that you can use netmask notation, which would specify the 192.168.0.x network as 192.168.0.0 mask 255.255.255.0, but in reality, this isn’t supported and won’t work. Although it is annoying that the Windows SDK doesn’t correspond to what works, it isn’t a serious problem because CIDR notation accomplishes exactly the same thing, is more compact, and is easier to understand. Ranges could be used as well. Strings designating IPv6 addresses are also supported.

  • LocalPorts–The local ports tell us what ports are affected by the rule. You can specify “*” for all ports, or you can specify an individual port. If you want to specify more than one port, a commaseparated list will do it (such as “7, 8, 9”). Unfortunately, ranges aren’t available for ports in this version. Another issue is that while it’s tempting to copy and paste the list of properties into your code and then set them one-by-one so you won’t forget anything, if you try to set the local or remote ports prior to setting the protocol to either TCP or UDP (6 or 17), the set_LocalPorts method will fail with an error of invalid parameter. Although we haven’t tried it, the same may be true of the ICMP types and codes. Just remember to set the protocol first, and things will work as expected. The following keywords are supported (Yariv and Mohan, personal communication):

    • RPC: a special keyword designating a port used by the RPC run-time DLL. When the RPC run-time DLL is using a dynamically assigned port to receive traffic for a given interface (RPC/TCP), it marks the socket in a special way that is detected by Windows Firewall. Specifying this keyword in the LocalPort attribute means the port is dynamically assigned and used for RPC purposes.

    • RPC-EPMap: a special keyword used to designate the RPC end-point mapper port. This is used by the RPCSS service when it uses TCP/135 for RPC/TCP end-point mapping and TCP/593 for RPC/HTTP end-point mapping. Instead of specifying this fixed port, by using this keyword the firewall is being notified by the RPCSS service of the right port it needs and dynamically opens/closes these ports on the fly as RPC interfaces are being registered/unregistered. This port keyword is used in the Windows Firewall out-of-box policy and should also be used for third-party cases where dynamic RPC is being used.

    • Teredo: a special keyword used to designate the dynamic ports the Teredo service is using to perform its NAT-traversal magic. By using this keyword, the firewall is being notified by the Teredo service of the right port(s) it needs and dynamically opens/closes these ports on the fly as Teredo is being utilized. This port keyword is used in the Windows Firewall out-of-box policy and should probably not be used in third-party scenarios.

  • Name–This is simply the name of the rule, specified by a string.

  • Profiles–The profiles property determines whether the rule applies to public, private, or domain profiles. This same set of properties shows up in the NLM APIs discussed earlier in this chapter.

  • Protocol–This is the protocol for your traffic. Typically, this would be 1 for ICMP, 6 for TCP, or 17 for UDP. IPv6 is specified by 41. Out of the 255 possible values, close to 140 are currently defined. You can find a complete and current list at http://www.iana.org/assignments/protocol-numbers. If there’s not an RFC listed along with the protocol, it’s probably something used in the early days of IP networking and hasn’t been seen since.

  • RemoteAddresses–The remote addresses follow the same rules as the local addresses, but there’s also a predefined string that is very useful: “LocalSubnet” means that we only want to accept local traffic.

  • RemotePorts–Like the LocalPorts property, this specfies which remote ports your application can access. For most server applications, this would be any port (or “*”), and for client applications, you’d specify the port the server listens on.

  • ServiceName–This specifies the name of the service the rule applies to. Many services run under the same application name, and this setting is needed to distinguish between the large number of services running under svchost.exe, to give the most common example. Once you understand how to create rules, we’ll show you how to apply this setting in Chapter 6, which covers services. If your service isn’t a sockets application, the default rule blocks everything, which is both easy and does exactly what you want.

    image from book
    Setting the Group and Interface Properties

    Setting the group property string is essential to being able to turn on and off all your rules in a group, and it ought to be as easy as doing this:

     BSTR Group = SysAllocString( L"My Rule Group" ); hr = pFwRuleIn->put_Grouping( Group );

    Unfortunately, it won’t work that way prior to service pack 1. What you actually need to do is this:

     BSTR Group =       SysAllocString( L"@c:\\scratch\\RuleSetResource.dll,-101" ); hr = pFwRuleIn->put_Grouping( Group );

    So just what is this DLL, and what’s the -101 for? Just follow these steps, and it isn’t all that onerous. First, create a new project under your solution. Make it a Win32 project, tell it you want a DLL, and just take all the defaults. None of the code is actually important. Next, right-click at the top of the new project, and tell it you’d like to add a new resource. It will ask you what type of resource; select “String Table.” Drill down into the new string table, and add a string for your group name. Take note of the string ID–the first will be 101, which explains the “-101” after the DLL name. What this also gives you, nearly for free, is an easy way to localize your application’s firewall rules for different languages. Finally, remember at setup time to place the DLL on the user’s system, and make sure the application setting your firewall rules has the full path to your resource DLL. If you’d like to learn more about the string syntax, see the Windows SDK documentation for SHLoadIndirectString.

    You can set the interface property using this code:

     variant_t vtInterfaceName("Local Area Connection"), vtInterface; long index = 0; SAFEARRAY *pSa = NULL; pSa = SafeArrayCreateVector(VT_VARIANT, 0, 1); if (!pSa)      _com_issue_error(E_OUTOFMEMORY); else {      hr = SafeArrayPutElement(pSa, &index, &vtInterfaceName);      if FAILED(hr)         _com_issue_error(hr);      vtInterface.vt = VT_ARRAY | VT_VARIANT;      vtInterface.parray = pSa; }

    image from book

Now that we’ve documented all the foibles of the NetFwRule object, using them is fairly simple. You call CoCreateInstance on as many NetFwRule objects as you need, set all the properties where the default isn’t good enough, get an interface to the rules collection, and add your rule(s). The code looks like this:

 hr = CoCreateInstance( __uuidof(NetFwRule),                        NULL,                        CLSCTX_INPROC_SERVER,                        IID_INetFwRule,                        (void**)&pFwRuleIn ); // Go set up all the rule properties // Now let's get a pointer to the rules INetFwRules* pFwRules; hr = pFwPolicy2->get_Rules( &pFwRules ); // Handle errors hr = pFwRules->Add( pFwRuleIn ); if(SUCCEEDED(hr))    printf( "Added inbound rule\n" ); else    printf( "Could not add inbound rule - err = %x\n", hr );

The last remaining task is really the hardest part: how to make a rule set for your application. For our example server, we created a simple TCP-based echo server. Although you won’t often see echo servers these days, they used to be very common and were used to establish network connectivity and a quick check of latency and available bandwidth. An echo server listens on a well-known, designated port (7) and sends back any ASCII string it was sent by the client.

Let’s look at what we need to do. For inbound connections, we have to allow traffic from any remote port to our local port 7. If the feature were available, we’d prefer to be able to say that we wanted to accept connections from dynamic ports–port numbers greater than 1023 (ports below that are reserved ports, although on Windows any application can use them). Unfortunately, port ranges aren’t available, so we’ll just have to take all ports. For a silly example like echo that can be used to eat up all our expensive bandwidth, it would be best to set the RemoteAddresses to “LocalSubnet.” Your choice of RemoteAddresses could be different, and is one of the more important decisions to make about your application.

Now that we can get inbound connections, we need another rule to specify where the replies can go. Our first outbound rule is to allow traffic from local port 7 to all remote ports on the local subnet, which is a straightforward reversal of the inbound rule. The second outbound rule illustrates a more subtle problem–we certainly don’t want to allow a situation where our echo server can be tricked into having an endless conversation with another echo server. To prevent this, we’ll add an outbound rule that disallows traffic from our port 7 to any remote port 7. If you’re well educated about networks, you might point out that spoofing a connection isn’t so easy with TCP, and this is mostly a problem with UDP–and you’d be right! However, this is just an example, and the point is that just because you need to reply to legitimate inbound connections, there might be some things you ought not reply to.

If you’re the inquisitive type, you might also be wondering why we’d go to all this trouble to restrict traffic to port 7 on our server. After all, the server only listens on port 7, and any connections attempted to other ports will just get met with a reset, so why not just allow all ports to all ports? The answer to this is that if an attacker did take over your application, one of the things they might do is bind a command shell to a port you might not notice–say, 54321. Your echo service obviously has no business accepting connections on port 54321, but if you allowed traffic to all ports, this is exactly what could happen. If you restrict the traffic to port 7, the attacker’s attempt to bind a command shell to port 54321 will end up failing the first time they call accept, and not knowing just what it is that made things break, they may just move along to a less resistant victim. Another common ploy would be to make an outbound connection to another port, but in this case, our outbound rule has restricted them to starting from port 7. If the attacker knows what is stopping them, only then could they bind their source port to port 7 and make the outbound connection. You could stop them from rebinding to port 7 by setting SO_EXCLUSIVEADDR, but you could get into issues trying to restart on failure. The bottom line here is that principle of least privilege is always a good thing, and this goes double if you’re dealing with networked applications.

If you’re writing a client application, you would just apply the same rules in reverse. Allow connections from any local port to the server’s port, and traffic from the server’s port back to any local port. If you’ve read (and followed) our advice on how to make firewall-friendly applications in the second edition of Writing Secure Code, writing firewall rules for your application should be easy. If you didn’t follow our advice and find yourself writing more than a dozen rules to cover a myriad of connections between a large number of ports on both ends, or if you forgot that sockets do go both ways and used two when you needed one, it might be time to refresh your knowledge of that topic and perhaps write your application to play more nicely with firewalls.

Working with Rule Groups

Once you have your rules created, the easiest way to manipulate them is to use the group interfaces exposed from the NetFwPolicy2 object. Unlike the rules themselves, the rule group interfaces will work with the plain group string, or the resource DLL reference. The following are equivalent for the demo rules:

 BSTR group = SysAllocString( L"SocketServer Example" ); BSTR group = SysAllocString( L"@c:\\scratch\\RuleSetResource.dll,-101" );

You have methods available to determine if the rule group is currently enabled and whether the rule group is enabled for each of the three profiles (domain, public, and private), and you can enable or disable your rule group for each or all of the three profiles with one call. Take a look at the CheckFirewallRuleSet sample application to see how to use the APIs. As with the rules, be mindful that a VARIANT_BOOL isn’t a bool, nor is it a BOOL. For example, this works:

 if( SUCCEEDED( pFwPolicy2->EnableRuleGroup( profileTypesBitmask,                                             group, VARIANT_TRUE ) ) ) {      printf( "Rule group now enabled\n" ); }

If you had passed “true” or “TRUE” to EnableRuleGroup, it would not have worked.

image from book
BOOL, bool, and VARIANT_BOOL

What’s in a Boolean? All of these are various integer types, and we might tend to treat them as interchangeable. For example, I might write:

 BOOL fFoo = true;

And expect it to work. The compiler may complain, and it will certainly complain if we try to assign TRUE to a bool, but it will indeed work as we expect. Unfortunately, if we mix and match bool, BOOL, and VARIANT_BOOL, it won’t always work, and if you write:

 VARIANT_BOOL fEnabled = true;

It will compile cleanly, and if you pass fEnabled to any of the set methods for the preceding rules, you’ll find that it won’t work. Despite having done a lot of work with integer manipulation and overflows, this caused me significant amounts of programmer astonishment! To save you time, trouble, and headaches, here’s what’s really going on:

Open table as spreadsheet

Type

Integer Representation

Value When True

Value When False

BOOL

signed int

1

0

bool

unsigned char

1

0

VARIANT_BOOL

signed short

-1

0

When armed with this information, we quickly see that if( VARIANT_TRUE == true) is false, since -1 != 1! While we’re on the topic of bools, you may often run into annoying warnings when you do something like assign the results of a Windows API that returns BOOL to your C++ bool. The right way to do this is:

 bool fRet = !!OpenProcessToken( ... );

What happens here is that the first unary not (!) performs an operator cast to type bool, and then the second unary not changes the result to what you expected. A less elegant way to do the same thing is:

 bool fRet = OpenProcessToken( ... ) ? true : false;

If you’ve been programming for Windows for a long time, you may recall back in the 16-bit days when TRUE was a 16-bit integer and was defined as -1. Because VARIANT_BOOL is descended from Visual Basic, it has kept its 16-bit heritage, and unfortunately VARIANT_TRUE is -1.

What should you do about all this? First, be aware of these differences. If something asks you for a VARIANT_BOOL, pass it either VARIANT_TRUE, or VARIANT_FALSE. If you’re writing code that deals with VARIANT_BOOL, and you’d like to be robust in the face of unsuspecting C++ programmers thinking that true might be 1, write your code like this:

 if( fEnabled != VARIANT_FALSE )

not like this:

 if( fEnabled == VARIANT_TRUE )

or better yet,

 if( fEnabled )

You can always count on false being 0, but you cannot count on anything else, although it is a happy side effect that I can cast a bool to a BOOL and back again without ever changing the value.

image from book

A documentation problem we uncovered is that IsRuleGroupCurrentlyEnabled is documented, whereas get_IsRuleGroupCurrentlyEnabled is what actually exists in the header and will compile. Hopefully, by the time you’re reading this, the documentation will have been updated and will be correct.



Writing Secure Code for Windows Vista
Writing Secure Code for Windows Vista (Best Practices (Microsoft))
ISBN: 0735623937
EAN: 2147483647
Year: 2004
Pages: 122

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net