Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.


Haskell Tool

FFFUU -- Fancy Formal Firewall Universal Understander

FFFUU logo

Build Status

Precompiled Binaries

A pre-compiled binary of fffuu for IPv4 and fffuu6 for IPv6 can be obtained from Bintray. A new binary is created on every push to master.

If you have trust issues with binaries, you can confine fffuu with AppArmor. fffuu only requires read access to the data you want to analyze (and nothing more). For example, given you saved fffuu to /home/diekmann/Downloads, you can put my apparmor profile to /etc/apparmor.d/home.diekmann.Downloads.fffuu and enforce it. Tested on Ubuntu 14.04 and 16.04.

Building from Source

Build uses cabal and supports the GHC 7.8.x, 7.10.x, and 8.0.1 series. Stack support is available. To get started, use the following commands:

$ cabal update && \
  cabal sandbox init && \
  cabal install --only-dependencies --enable-tests && \
  cabal build && \
  cabal test

With the build command, cabal produces a binary which can be invoked like this:

$ ./dist/build/fffuu/fffuu --help
FFFUU -- Fancy Formal Firewall Universal Understander

Usage: fffuu [--verbose] [--ipassmt STRING] [--routingtbl STRING]
             [--table STRING] [--chain STRING] [--service_matrix_sport INTEGER]
             [--service_matrix_dport INTEGER]... STRING

Available options:
  -h,--help                Show this help text
  --verbose                Show verbose debug output (for example, of the
  --ipassmt STRING         Optional path to an IP assignment file. If not
                           specified, it only loads `lo = []`.
  --routingtbl STRING      Optional path to a routing table.
  --table STRING           The table to load for analysis. Default: `filter`.
                           Note: This tool does not support packet modification,
                           so loading tables such as `nat` will most likeley
  --chain STRING           The chain to start the analysis. Default: `FORWARD`.
                           Use `INPUT` for a host-based firewall.
  --service_matrix_sport INTEGER
                           Source port for the service matrix. If not specified,
                           the randomly chosen source port 10000 is used. TODO:
                           maybe use an ephemeral port ;-).
  --service_matrix_dport INTEGER...
                           Destination port for the service matrix. If not
                           specified, SSH and HTTP (22 and 80) will be used.
                           Argument may be repeated multiple times.
  -h,--help                Show this help text
  STRING                   Required: Path to `iptables-save` output. This is the
                           input for this tool.


Load the output of iptables-save into our tool. The tool will dump lots of information about your ruleset.

./dist/build/fffuu/fffuu /path/to/iptables-save.txt

To improve the analysis, you can provide an IP assignment file. This optional file describes which source ip ranges are expected on a certain interfaces. For example:

lo = []

If your system only has a single routing table, you can provide the output of ip route using --routingtbl <FILE PATH> to further improve the results. A comparison to the loaded IP assignment will be displayed as a side-effect.

WARNING: This can produce boguous results if your system has more than one relevant routing table or if your ipassignment specifies too small ranges.


Try this:

./dist/build/fffuu/fffuu ../thy/Iptables_Semantics/Examples/Parser_Test/data/iptables-save --verbose

This example file is a nonsense config we use to stress the parser.

Example: Input / Output

Try this:

./dist/build/fffuu/fffuu --ipassmt ipassmt_tumi8 ../thy/Iptables_Semantics/Examples/TUM_Net_Firewall/iptables-save-2015-05-15_15-23-41_cheating

This will generate lots of output.

If you visualize the service matrix for ssh with your favorite graph visualization tool, you will see something similar to this (png on imgur). fffuu gives you the minimal graph representation. Yes, the access matrix is really that complicated. This is the intrinsic complexity of this firewall, just check the 5k rules by hand ;-)

We only support packet filtering, i.e. no packet modification. Consequently, we only officially support the filter table. If you like to live dangerously, you can try different tables. The tool will do a runtime check if everything in the requested table is supported. For example, spoofing protection can be done in the raw table. Try this:

./dist/build/fffuu/fffuu --table raw --chain PREROUTING --ipassmt ipassmt_sqrl ../thy/Iptables_Semantics/Examples/SQRL_Shorewall/2015_aug_iptables-save-spoofing-protection

Output (featuring the --verbose flag)

Small Example

The fffuu tool is really useful for network firewalls (e.g. on your perimeter router). Here is a contrived example (compiled from many real-world rule sets):

:DOS_PROTECT - [0:0]
:GOOD~STUFF - [0:0]
-A FORWARD -p tcp -m multiport ! --dports 80,443,6667,6697 -m hashlimit --hashlimit-above 10/sec --hashlimit-burst 20 --hashlimit-mode srcip --hashlimit-name aflood --hashlimit-srcmask 8 -j LOG
-A FORWARD ! -i lo -s -j DROP
-A FORWARD -i inteneral -s -j ACCEPT
-A FORWARD -s -d -j DROP
-A FORWARD -p tcp -d -j ACCEPT
-A FORWARD -i dmz -p tcp -s -j ACCEPT
-A GOOD~STUFF -p icmp -m state --state RELATED -j ACCEPT
-A DOS_PROTECT -i eth1 -p icmp -m icmp --icmp-type 8 -m limit --limit 1/sec -j RETURN
-A DOS_PROTECT -i eth1 -p icmp -m icmp --icmp-type 8 -j DROP

What does the ruleset do? The service matrix looks as follows:

a |-> { ..}
b |-> { ..}
c |-> { ..}
d |-> { ..} u { ..} u { ..} u { ..}


Let’s visualize this: fffuu dmz example hosted on imgur

Ignore the range at the bottom. We can see that the firewall implements the textbook DMZ architecture: Internet (cloud) on the top, internal machines on the left, servers (DMZ) on the right. Yay!

Another Small Example

What does the following firewall do?

:CHAIN - [0:0]
-A CHAIN -p tcp -m tcp --sport 22 -j RETURN
-A CHAIN -p udp -m udp --dport 80 -j RETURN

Here is the output of fffuu:

DROP       udp  --     dports: 0:79
DROP       udp  --     dports: 81:65535
DROP       tcp  --     sports: 0:21 
DROP       tcp  --     sports: 23:65535 
ACCEPT     all  --  

Answer: it drops everything which is not udp dst port 80 or tcp src port 22.

Longer Real-World Example

We will analyze the ruleset of a NAS. The NAS runs a host-based firewall, so we are interested in the INPUT chain. Here is the ruleset:

:OUTPUT ACCEPT [1745:334865]
:DOS_PROTECT - [0:0]
-A DEFAULT_INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A DEFAULT_INPUT -p tcp -m multiport --dports 873,3260,3261,3262,3240:3259,21,548,111,892,2049,443,80,3493,3306 -j DROP
-A DEFAULT_INPUT -p tcp -m multiport --dports 22,23 -j DROP
-A DEFAULT_INPUT -p udp -m multiport --dports 68,67,123,514,19999,5353,161,111,892,2049 -j DROP
-A DOS_PROTECT -i eth1 -p icmp -m icmp --icmp-type 8 -m limit --limit 1/sec -j RETURN
-A DOS_PROTECT -i eth1 -p icmp -m icmp --icmp-type 8 -j DROP
-A DOS_PROTECT -i eth1 -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK RST -m limit --limit 1/sec -j RETURN
-A DOS_PROTECT -i eth1 -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK RST -j DROP
-A DOS_PROTECT -i eth1 -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -m limit --limit 10000/sec --limit-burst 100 -j RETURN
-A DOS_PROTECT -i eth1 -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -j DROP
-A DOS_PROTECT -i eth0 -p icmp -m icmp --icmp-type 8 -m limit --limit 1/sec -j RETURN
-A DOS_PROTECT -i eth0 -p icmp -m icmp --icmp-type 8 -j DROP
-A DOS_PROTECT -i eth0 -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK RST -m limit --limit 1/sec -j RETURN
-A DOS_PROTECT -i eth0 -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK RST -j DROP
-A DOS_PROTECT -i eth0 -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -m limit --limit 10000/sec --limit-burst 100 -j RETURN
-A DOS_PROTECT -i eth0 -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -j DROP

The NAS has the following interesting services:

  • ssh - TCP port 22. For management purposes, was enabled for an update, should be blocked now.
  • http - TCP port 80. Should be blocked, there should not be any service listening.
  • http - TCP port 8080. The management web interface. Should only be accessible from the local LAN.

We run the following:

./dist/build/fffuu/fffuu --chain INPUT --service_matrix_dport 22 --service_matrix_dport 8080 --service_matrix_dport 80 ../thy/Iptables_Semantics/Examples/Synology_Diskstation_DS414/iptables-save_jun_2015_cleanup

Output (featuring the --verbose flag)

We did not specify an ipassmt. The tool tells us:

WARNING: no IP assignment specified, loading a generic file

This means, the tool only assumes that lo corresponds to, but nothing more. But the ruleset also matches on eth0 and eth1. The tool has no information about these interfaces, it will do a sound overapproximation. The result will be sound, but chances are, that the tool assumes that the firewall permits too much. Note: Assuming that the firewall (in doubts) permits too much is the conservative approach when verifying that the firewall actually blocks the right things. If you want better results, provide the valid source IP address ranges for the interfaces. In this example, we are fine. If you do heavy matching on -i in your ruleset, you may want to specify an ipassmt to improve accuracy of the results.

To simplify the ruleset, fffuu abstracts over all match conditions which are not essential for our analysis. It does an overapproximation, that means, it answers the question "which packets might be potentially allowed by the firewall?". Here is the simplified, abstracted ruleset (in pseudo iptables -L -n format):

ACCEPT     all  --      in: lo   
ACCEPT     tcp  --      dports: 22
DROP       tcp  --      dports: 873
DROP       tcp  --      dports: 3240:3262
DROP       tcp  --      dports: 21
DROP       tcp  --      dports: 548
DROP       tcp  --      dports: 111
DROP       tcp  --      dports: 892
DROP       tcp  --      dports: 2049
DROP       tcp  --      dports: 443
DROP       tcp  --      dports: 80
DROP       tcp  --      dports: 3493
DROP       tcp  --      dports: 3306
DROP       tcp  --      dports: 22:23
DROP       udp  --      dports: 67:68
DROP       udp  --      dports: 123
DROP       udp  --      dports: 514
DROP       udp  --      dports: 19999
DROP       udp  --      dports: 5353
DROP       udp  --      dports: 161
DROP       udp  --      dports: 111
DROP       udp  --      dports: 892
DROP       udp  --      dports: 2049
ACCEPT     all  --    
DROP       all  --      

We can see that fffuu has split the multiport matches into individual rules and that the ruleset has been unfolded into one linear ruleset (the user-defined DEFAULT_INPUT and DOS_PROTECT chains are gone). This makes internal processing simpler; it does not change the behavior. Now about the 'overapproximation': The original ruleset did a lot of rate limiting and drops packets which exceed the limit. To answer the question about potentially allowed packets, we must assume that the rate limiting will not drop us (i.e. our packets make it through the DOS_PROTECT chain without being dropped). We can see that the simplified ruleset has completely eliminated the rate limiting. Finally, let's look at the last rules of the simplified ruleset. The last rule (drop all) corresponds to the butlast rule of the DEFAULT_INPUT chain. This rule can be called a default policy. Since it matches all packets, fffuu stopped processing at this point. Note that the last rule of DEFAULT_INPUT (drop all from eth0) and everything after that has been deleted.

Let's get to the interesting services on our NAS. fffuu will print three service matrices for TCP destination ports 22, 8080, and 80. As source port, the random, not-so-ephemeral source port 10000 was selected by fffuu. As we can see, the ruleset does not match on sports (source ports), so any value is fine here. Here are the service matrices:

=========== TCP port 10000->22 =========
a |-> { ..}


This one is for ssh. It reads as follows. First, the nodes are defined. The nodes are grouped IP ranges. Here, we only have one node. This one node corresponds to the complete IPv4 address range { ..}. This means, all IP addresses are treated equally for ssh. Our policy wants them all to be dropped. Let's see. Nodes are given unique names. Here, it is a. Next, the edges are defined. Each pair (a,b) in the edges means that the IP range which corresponds to a can access b. Here we have (a,a). This means everyone can access everyone. Wait what? We want ssh to be blocked. Though it is blocked in the multiport rule, this rule is shadowed by the third rule of DEFAULT_INPUT which allows ssh. This was an artifact of the previous system update. Flaw found and scheduled for fixing ;-)

By the way, the service 'matrix' is actually a graph and graphviz can visualize these things. TODO: export to graphviz automatically.

Let's look at the next service matrix.

=========== TCP port 10000->8080 =========
a |-> { ..} u { ..}
b |-> { ..} u { ..} u { ..}


This one is for the webinterface at port 8080. Now we have two nodes, which means the firewall distinguishes two different classes of IP addresses. The first class of IP address (called a) consists of all localhost IPs and the local range. We only wanted the webinterface reachable from the local range. The first rule of DEFAULT_INPUT additionally accepts everything from lo, so this should be fine. The second node (called b) is the rest of the IPv4 address space. We want this to be dropped. Let's look at the edges. As we can see in the first vertex (a,a): Only the good lo + local LAN range can reach the machine (which is itself located in the a range). The second edge (a,b) basically tells: The NAS itself can freely access any IP (which is fine).

Let's look at the third matrix.

=========== TCP port 10000->80 =========
a |-> { ..} u { ..}
b |-> { ..}


This one is for port 80, which should be dropped. Exercise to the reader: Read the service matrix. Indeed, port 80 is blocked (except for packets from lo).


Q: The tool doesn't show anything for my firewall.

A: Our tool loads the filter table and the FORWARD chain by default. If you have a host-based firewall, you may probably want to load the INPUT chain.