Wednesday, September 8, 2010

IPFilter: Designing for security

In the last 15 years, IPFilter has been popular enough that it has inspired at least one other firewall to imitate its configuration syntax (pf) and seemingly the basis of another in progress (npf). The examples that I've seen used for each indicate to me that neither of them is intended to be used in a manner that focuses on security as the prime goal of the design. That is distinct from saying that they can't be used to implement or achieve security.

One of the central themes in security is the ability to audit. For IPFilter, my interpretation of this requirement is that it is absolutely necessary to be able to audit the running configuration of the machine against what is found in its configuration file otherwise how else do have any assurance of what security policy is actively being applied to packets? In this instance, rule expansion where one rule with 5 ports becomes 5 rules or when you're using a text expression that is compiled into opcode makes it significantly harder to verify what's loaded into the kernel with what's in your configuration file.

To successfully audit an IPFilter configuration, three steps are required:
(1) collect the sorted rules from the kernel
(2) generate a list of sorted rules from the configuration file
(3) compare the results of (1) and (2)

Why do I mention "sorted rules"? The order in which rules are retrieved from the kernel and their grouping (all input rules and/or or output rules) is not likely to match the order in which they appear in the configuration file.

To give a brief example..

An extract of my configuration file looks like this:


pass in quick on lo0 all
pass out quick on lo0 all
pass out quick on nfe0 proto tcp all flags F/F
pass out quick on nfe0 proto tcp all flags R/R
#
block in log all
block in log quick from any to pool/666
block out quick log from any to pool/666
block in quick log from pool/666 to any
block out quick log from pool/666 to any


If I parse that configuration file and sort it using ipf, I get:

$ ipf -nvf /tmp/foo | sed -e 's/(!)//' | sort

block in log all
block in log quick from any to pool/666
block in log quick from pool/666 to any
block out log quick from any to pool/666
block out log quick from pool/666 to any
pass in quick on lo0 all
pass out quick on lo0 all
pass out quick on nfe0 proto tcp from any to any flags F/F
pass out quick on nfe0 proto tcp from any to any flags R/R


If I dump the kernel configuration and process it like above:


# ipfstat -io | sort
block in log all
block in log quick from any to pool/666
block in log quick from pool/666 to any
block out log quick from any to pool/666
block out log quick from pool/666 to any
pass in quick on lo0 all
pass out quick on lo0 all
pass out quick on nfe0 proto tcp from any to any flags F/F
pass out quick on nfe0 proto tcp from any to any flags R/R


Thus it is immediately possible to compare both. If I were to audit what is in the file itself rather than what the parser interprets it,
then the text I would be comparing with what is output from the kernel would be:


block in log all
block in log quick from any to pool/666
block in quick log from pool/666 to any
block out quick log from any to pool/666
block out quick log from pool/666 to any
pass in quick on lo0 all
pass out quick on lo0 all
pass out quick on nfe0 proto tcp all flags F/F
pass out quick on nfe0 proto tcp all flags R/R


Whilst that isn't the same as what ipfstat reports, it is very close and allows for a simple audit with diff to determine what the differences are and if they're proper. In this example, the only difference is "all" vs "from any to any". "all" becomes "from any to any" whenever there is a packet attribute that is required for matching that is not an address or port number.

This is a very simple requirement that actually delivers a very strong foundation.

For those familiar with IPFilter, you may be wondering why didn't I apply the same rationale to ipnat. Indeed when I review it, it is most definitely lacking a mechanism to display only the loaded NAT rules or only the active sessions. Currently doing "ipnat -l" lists both at once. Whilst the NAT functionality isn't directly responsible for enforcing security and thus exposed to the requirement of being able to be audited, it does play a part and should be more friendly in this area.

An additional benefit of this design is that removing a rules from the kernel's configuration is rather easy. For example, to remove all of the current rules you can perform a flush (ipf -Fa) or remove rules individually (ipfstat -io | ipf -rf -). The former is more common because it is both simpler and there are fewer complications when using rule groups. As it is, the design allows for tools to interrogate the kernel and the result of that interrogation is valid input that can then be reused.

2 comments:

Unknown said...

During my sysadmin days, firewall ruleset auditing (think rulesets with tens of grops and thousands of rules, *per interface*) was done using ruleset evaluation with firing set of hardcoded packet traces and permuted packet traces against the ruleset and checking if they passed or not.

Anonymous said...

What about having a flag that causes rules from the config file to be expanded (so that the reported rules are "any to any" instead of "all")?

That would allow both compressed syntax and reasonable auditing. Sure, it's not quite as secure (in that I have to trust the tool to expand the rules properly), but it does make life easier.