One of the cool things about finding something cool is learning how much more cool it really is as you become experienced with it. When I first got to the point where I could do what I needed with iptables/NetFilter I was impressed with the capabilities they have. I was so impressed I wrote a Cool Solution showing how to build a basic firewall with iptables. In the year since I wrote that Cool Solution I've had occasion to do other things with iptables that may be beyond the norm but they are certainly cool and probably of use to many.
For the record, the other solution was based directly and closely on the work I did on the firewall for my home network (where I also host my personal domain). The examples in this Cool Solution are mostly based directly and closely on enhancements I've made to my home firewall since the last article. But, some of the examples here are based on things that someone asked me about and I thought iptables/NetFilter had a cool way to solve it. The last example here explains how I modified the script from my other Cool Solution to use it with dynamic public IP addresses and KVM virtual machines on my laptop. In other words, everything in the other Cool Solution and this one is in real use.
Here's a list of what's in this cool solution:
- Controlling traffic by a user or group ID
- Deleting a rule
- Inserting a rule
- Changing a rule in place
- Filtering traffic containing certain text
- TCP frames with illegal flag combinations
- Blocking an address range
- Monitoring specific traffic with minimal overhead
- Working with a dynamic public IP address
I mentioned in a few places in the other solution that you need to be careful to have rules in a chain in the right order, or even in the right table and chain order to achieve your goals. In the script from the other solution many of the rules can be placed in any order, e.g. you can put the SMTP DNAT rule before the HTTP DNAT rule or vice versa and it makes no difference. For most of the examples here that isn't true and you have to put the rule in the correct place for it to work.
To illustrate, consider one of the cases in the other solution where order is important: blocking an IP address. Using the example in the other solution, imagine you are being constantly attacked by a host at 10.220.231.236. Well, the common place to block a host is the INPUT chain of the filter table. If you add the host blocking rule as follows with the other solution's script already started the blocking rule will not work:
iptables -A INPUT -i $IF_PUB -s 10.220.231.236 -j REJECT --reject-with icmp-host-prohibited
This is because the -A puts the rule at the end of the INPUT chain and the firewall operates waterfall style, a packet is tested against each rule in order and when it matches an ACCEPT rule the packet is not tested against any other rule in that chain.
So, all the -j ACCEPT rules already in the INPUT chain will apply before the new -j REJECT rule we added. So, the rule to block host 10.220.231.236 must come before any -j ACCEPT rule that packets from that host would match. That might even mean the INPUT chain of the filter table is the wrong place for it.
The PREROUTING chain of the NAT table is where all the DNAT rules are placed in my other cool solution. They do things like forward port 80 on the firewall's public interface to port 80 on a private host where a web server is running. Those packets don't go through the INPUT chain in the FILTER table because their destination changes to a private host before reaching the FILTER table. The INPUT chain is for packets with a destination of the firewall host itself. So, my other cool solution contains two rules for each blocked host or network. One in the FILTER table's INPUT chain for anything targeting the firewall host that isn't DNAT and one in the FILTER table's FORWARD chain for anything that is DNAT.
For many of the following examples the rule shown must be positioned at a specific place in the chain.
Lock a user down
You can order rules such that you can permit a user to login to a host via, say, ssh but then prevent that user from connecting to anything else on or off the box via IP. Exposing the ssh server is the same as in my other cool solution. You have a rule to permit inbound tcp connections to port 22/tcp and a rule to permit all inbound traffic in established sessions. But, you also have a rule to permit all outbound traffic from the firewall host. So, once connected to the firewall via ssh a user can connect to anything that the firewall has a route to.
NetFilter has an owner module that can be used to create per-uid and per-gid rules. Let's say there is a user joe, with uid 1002. He can connect to ssh with the rules mentioned above. What if you wanted joe to be able to login and do stuff on the firewall host but have no access to anything on your private network. Well, this will do it:
iptables -A OUTPUT -m owner --uid-owner 1002 -j DROP
That rule MUST come before the one that permits everything outbound from the server. Using the shell script functions in the firewall in the other cool solution as an example, the placing of this command would be as follows:
iptables -A OUTPUT -m owner --uid-owner 1002 -j DROP
You can, of course, make the rule contain other restrictions and have multiple rules with different restrictions for the same uid. For example, you could have a rule that drops outbound traffic to port 80/tcp for a given uid. And another rule to drop traffic to port 25/tcp for the same user. Or, you could have a drop everything rule and put an accept rule before it to explicitly declare what traffic is permitted for joe.
You can also match on --gid-owner (group ID), --pid-owner (process ID), --sid-owner (session ID), --cmd-owner (command name). The last one is odd but pretty cool. For example, you could do things like allow firefox access out to port 80/tcp only and konqueror access out to port 443/tcp only. On the 2.6.22 kernel on my OpenSuSE 10.3 system pid, sid and cmd are broken for SMP kernels.
When I wrote my other iptables cool solution I expressed a preference for not dropping packets and, instead, responding as a normal TCP/IP stack would if the thing being accessed was not present. The examples above break this philosophy by dropping packets when the source process has a specified uid. I broke with my philosophy to make it possible to use one rule and keep it simple. If we wanted to have the appropriate response for each possible packet type we'd need a lot of rules, one for tcp, one for udp, one for icmp, etc and we would still have to drop packets that iptables can't classify. So, for this case I'll concede I am a hypocrite.
Oops I mis-typed a rule
The -A in almost all of the rules I've shown in both solutions means: Add the rule to the end of the specified chain in the specified table. If you have a rule you want to remove from the current configuration then you can flush all the tables and add all the rules back except for the one you don't want. Or, you can remove the unwanted rule with the -D option. You simply specify the rule the same way as was used to create it. For example, if you typed the wrong UID in the rule above you can remove the incorrect rule with:
iptables -D OUTPUT -m owner --uid-owner 1002 -j DROP
The only difference is the -A changed to a -D.
So I deleted a mis-typed rule, I need the correct version at the same place in the chain
Remember, rules are used in order. So, if you are deleting a rule to correct it you may need a way to put a rule in a specific place in a chain. For that we use -I before the chain then specify the rule position:
iptables -I OUTPUT 1 -m owner --uid-owner 1003 -j DROP
This puts the rule in at the top of the output chain (position 1).
Couldn't I just change the rule in-place
You can replace a rule by its number in its chain. For example, to replace the fifth rule in the input chain:
iptables -R INPUT 5 -p tcp --dport 80 -j ACCEPT
This replaces whatever the fifth rule was with one that permits traffic on port 80/tcp. Note that you don't specify the old rule, only the old rule's position and the new rule details.
My web server has no DLLs
My first enhancement of the original firewall script was to find a way to reject abusive traffic to my web server based on the request rather than the IP address it came from. I see a lot of requests to execute a function in some DLL. I use Apache on Linux. Before getting into details, I only use rules like this for persistent and specific methods of abuse. In general, the best place to start when controlling abusive requests to your web server is with configuration on your web server.
Well, if it makes more sense to filter on the firewall you can have NetFilter do a string search in a packet and act on it if certain things are found.
iptables -A INPUT -m string --algo bm --string .dll -j REJECT --reject-with-tcp-reset
That's the basic syntax but it's a bad rule as written. It searches the whole packet and will, for example, catch packets to our mail server discussing some file.dll. It would be bad for connections to our mail server to arbitrarily be reset. The easiest place to improve the rule is to have it only apply to traffic being forwarded to our web server on the private network:
iptables -A FORWARD --p tcp --dport 80 -m string --algo bm --string .dll -j REJECT â€“-reject-with-tcp-reset
It's also bad to string search the entire scope of every incoming packet to port 80 when the request URL can be found at fairly predictable locations. Fortunately the string module lets you specify the search scope with â€“from and --to:
iptables -A FORWARD -p tcp --dport 80 -m string --algo bm --from 54 --to 128 --string .dll -j REJECT --reject-with-tcp-reset
That will scan incoming packets to port 80/tcp for the text .dll between the 54th and 128th byte in the packet only. The --algo
specifies the string search algorithm Boyer-Moore in this case. Look it up on Wikipedia, it's an interesting...sorry...cool solution ;-) The other supported algorithm is kmp: Knuth-Morris-Pratt. You can find it on Wikipedia as well.
Be careful how you use string search. If the string being searched for might be in wanted packets that will match the other elements of the rule then the rule will cause unwanted failures. Be sure to use the scoping features like protocol, port, address, interface, byte range, etc to avoid false detection. Be aware that the string being searched for is case dependent, you may need a rule for .dll and another for .DLL.
This is another position sensitive rule. It has to be placed before anything that would accept the same traffic. From the example, the string search rule must come before the rule that accepts all traffic on port 80/tcp and anything that accepts established sessions.
You can't SYN and FIN in the same packet
There are some published exploits that take advantage of poor state validation in some IP stacks. For example, a packet with an invalid combination of tcp flags, such as SYN and FIN in the same packet. SYN starts the creation of a connection (synchronisation) and FIN closes a connection (finish/final). There is a -â€“tcp-flags option in the tcp module:
iptables -A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP
Note that the SYN,FIN appears twice. The first time is a bit mask, the second a comparison. That allows rules to test for flags being specifically on or off. In the example we are testing if the SYN flag is set at the same time as the FIN flag. But, if we'd wanted to drop packets where the URG(ent) flag was set but the ACK, SYN and FIN flags were all clear:
iptables -A INPUT -p tcp --tcp-flags ACK,SYN,FIN,URG URG -j DROP
What that means is that we consider the ACK, SYN, FIN and URG flags looking for only the URG one being set.
This is also a position sensitive rule. If it comes after something that matches other attributes of the packet then the flags test will never take place. So, it must come before any service matching rules like -â€“dport http and before any rules that match generically, e.g. Established sessions.
I want to block some addresses on a network but not all
When I review my own firewall and web server logs for abuse I often end up doing a whois lookup on the source IP address for the perceived abuse. Whois lookups are a cool solution all on their own. My preference for whois lookups is http://www.whois.sc/ which is really http://www/domaintools.com/
Primary IP address space allocations are often sub-divided into smaller blocks for local assignment. So, some large backbone provider might split its address space into a number of CIDR blocks for its large subscribers, often smaller backbone providers and large ISPs. They may then sub-divide the address space further for companies that subscribe with them or for regional use. I've seen cases where a given usage of IP addresses isn't a complete CIDR subnet. For example, a smaller ISP with one /22 subnet that divides it out to three connection points of 340 node addresses each on the same /22 network...I know...but there really are cases like that. A whois search on an IP address will often reveal this non-subnet based division by showing the purpose specific range of addresses containing the problem IP address. In those cases where I have been attacked from a node in a use specific of a much larger subnet I really don't want to block the entire subnet. iptables let's me specify a range of addresses in a rule:
iptables -A FORWARD -m iprange --src-range 10.0.5.25-10.0.7.33 -j REJECT --reject-with icmp-host-prohibited
iptables -A INPUT -m iprange --src-range 10.0.5.25-10.0.7.33 -j REJECT --reject-with icmp-host-prohibited
It's another position dependent rule placed in the FILTER table FORWARD chain for DNAT and INPUT chain for access to the firewall host.
I just want to see what would match a rule without logging or affecting packets
You can have a rule with no jump target (-j) and anything that matches will just update the statistics for that rule:
iptables -A INPUT -p tcp
That will have the effect of creating nothing more than a counter for tcp packets targeting the firewall host. It's also position
sensitive. If you want to count all tcp packets you will have to place it before any service specific matches and any established session matches, e.g. in the PREROUTING chain of the NAT table or earlier. On the other hand, you might want to use a rule like this to see what some other rule is ignoring. Take the rules in the other solution that ACCEPT ICMP traffic. They don't accept all ICMP type codes, so you could count the ICMP traffic that doesn't get matched by placing this after all the other ICMP rules:
iptables -A INPUT -p icmp
Or, you could count attempts to attack one Windows networking attack vector without having to log it by placing this before the rule that rejects all other tcp traffic with a tcp-reset:
iptables -A INPUT -p tcp --dport 139
My public interface has a dynamic address
My other iptables/NetFilter cool solution assumes the public and private interfaces have static IP addresses. I recently started using KVM and QEMU for virtual machines on my laptop...now there's a cool solution. It's really necessary to have Internet access for the VMs. Providing functional networking for my QEMU VMs meant creating a bridge interface and taps on the host. The QEMU/KVM guests have NICs bound to the taps. My laptop has wired and wireless physical interfaces. Depending on where I am using the laptop one, the other or both of these might be active with a default route and assigned a dynamic IP address. The script in the other Cool Solution will not work for that scenario but it doesn't require much to make it work and I don't want to waste time re-configuring my firewall every time I change location.
The first part of the other solution that will not work is source NAT. The SNAT rule specifies the IP address to translate an outgoing routed packet's source address to. We can avoid the need to know the network or IP address on the public interface by using MASQUERADE instead of SNAT:
iptables -t nat -A POSTROUTING -s $NET_PRV1 -o $IF_PUB -j MASQUERADE
We are still specifying a known private network address but we now only have to specify the interface the address translation will take place on.
Remember the laptop has two physical interface, wired and wireless. Assume the wired is eth0 and the wireless eth1. This works:
iptables -t nat -A POSTROUTING -s $NET_PRV1 -o eth0 -j MASQUERADE
iptables -t nat -A POSTROUTING -s $NET_PRV1 -o eth1 -j MASQUERADE
I assume this works because a MASQUERADE rule is only applied if the outbound interface actually has an IP configuration with a default route. So it doesn't matter how many masquerade rules you have, the first functional one is used.
Almost all of the rules in the other solution that specify an IP address do not strictly require that it be specified. You could make most of the rules work by just specifying the interface and the other attributes. For example, the SSH service exposed on the non-standard port 2202/tcp in the other solution looks like this:
iptables â€“A INPUT -i $IF_PUB -p tcp -d $IP_PUB -dport 2202 -j ACCEPT
If you remove the highlighted section you get:
iptables -A INPUT -i $IF_PUB -p tcp -dport 2202 -j ACCEPT
That makes the rule apply to any incoming traffic on port 2202/tcp rather than only that with a destination of the host's IP address.
I haven't tested it but I see no reason that the same would not work for DNAT (port forwarding). For example, exposing a web server on the private network via the host's unknown public IP address:
iptables -t nat -A PREROUTING -i $IF_PUB1 -p tcp --dport http -j DNAT --to 192.168.10.4
At that point I've given you all the new things I've done with iptables/NetFilter in the year since writing the other Cool Solution. I hope they help you with problems you are dealing with or give you ideas for new ways of doing things. However, these two Cool Solutions still only scratch the surface of what you can do with iptables. I still haven't invested time to look at the mangle and raw tables but I know you can use the mangle table and connection marking to short circuit the established session rules.
When I wrote the other Cool Solution I said that the man page for iptables really only documents the syntax of the command and doesn't help with formulating a firewall. Once you know how to compose a firewall with iptables the man page is a lot more revealing. I had considered reviewing the man page in full and devising scenarios for just about every option but I don't think that it would be helpful for me to guess at uses I haven't tried in the real world. Hopefully these two cool solutions give you enough insight to permit you to use the iptables man page to solve problems I haven't dealt with. Please post your own examples as comments below and I'll extend these documents as I come up with new uses of my own.
I acknowledge the request for iproute2 solutions posted in a comment to the other Cool Solution. I'm not in a position to do that at this point due to not having a personal use for it that would give me the expertise to do it justice in an article.