Posted by chantra on December 14th, 2006
- Iptables: How-to Share your internet connection
- Iptables: How-to Share your internet connection — page 2
- Iptables: How-to Share your internet connection — page 3
- Iptables: How-to Share your internet connection — page 4
3.3. Defining custom chains
In order to get a easier to maintain iptables script, it is handy to define some custom chains, also called user-defined chains. This way, you can gather common actions into 1 chain, then, using our target switch (-j) we will be able to send packets that match specific rules to that target.
In order to create a user-defined chain, we need to use:
iptables -N chain_name
and then simply add rules to that chain using the usual:
iptables -A chain_name [rules ...] -j target
Okie, now that this is explained, we are going to create 3 user-defined chains which are going to log packet matching rules to be sent to this specific chain:
- Firewall: is going to log packets by prepending "Firewall: " and DROP them, as you will see, this will only deal with ICMP
- Rejectwall: is going to log packets (prepending "Rejectwall: ") that were not accepted my any previous rules
- Badflags: is going to log packets which TCP flags are not properly set. Some kind of packets are usually used during attack. (prepending "Badflags: ")
The bit of code that deals with the chain creation and which append rules to it is:
# Here we define a new chain which is going to handle
# packets we don't want to respond to
# limit the amount of logs to 10/min
$IPT -N Firewall
$IPT -A Firewall -m limit --limit 10/minute -j LOG --log-prefix "Firewall: "
$IPT -A Firewall -j DROP
# log those packets and inform the sender that the packet was rejected
$IPT -N Rejectwall
$IPT -A Rejectwall -m limit --limit 10/minute -j LOG --log-prefix "Rejectwall: "
$IPT -A Rejectwall -j REJECT
# use the following instead if you want to simulate that the host is not reachable
# for fun though
#$IPT -A Rejectwall -j REJECT --reject-with icmp-host-unreachable
# here we create a chain to deal with unlegitimate packets
# and limit the number of alerts to 10/min
# packets will be drop without informing the sender
$IPT -N Badflags
$IPT -A Badflags -m limit --limit 10/minute -j LOG --log-prefix "Badflags: "
$IPT -A Badflags -j DROP
As you can see, there is a new target (action), namely LOG. LOG is a specific target that logs the packet to /var/log/messages usually. LOG is a non-terminating target, this means that the packet is going to continue to the next rule after being logged.
by using the --log-prefix you can specify what is going to be prepended to your log.
So let's take the example of chain "Firewall".
First we create the chain: $IPT -N Firewall
Then, we ask the kernel to log the packet and to prepend "Firewall: " to the log string. But because we don't want our logs to be flooded by such logs, we cap the number of logs related to the Firewall chain to 10/minute: $IPT -A Firewall -m limit --limit 10/minute -j LOG --log-prefix "Firewall: "
Finally, after we logged the packet, we are simply going to DROP it: $IPT -A Firewall -j DROP
3.4. Using those rules
Creating user-defined chain will now make it easier and faster for us to operate specific actions on packets.
Let's go through the block of statements related to bad TCP flags:
# A list of well known combination of Bad TCP flags
# we redirect those to the Badflags chain
# which is going to handle them (log and drop)
$IPT -A INPUT -p tcp --tcp-flags ACK,FIN FIN -j Badflags
$IPT -A INPUT -p tcp --tcp-flags ACK,PSH PSH -j Badflags
$IPT -A INPUT -p tcp --tcp-flags ACK,URG URG -j Badflags
$IPT -A INPUT -p tcp --tcp-flags FIN,RST FIN,RST -j Badflags
$IPT -A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j Badflags
$IPT -A INPUT -p tcp --tcp-flags SYN,RST SYN,RST -j Badflags
$IPT -A INPUT -p tcp --tcp-flags ALL ALL -j Badflags
$IPT -A INPUT -p tcp --tcp-flags ALL NONE -j Badflags
$IPT -A INPUT -p tcp --tcp-flags ALL FIN,PSH,URG -j Badflags
$IPT -A INPUT -p tcp --tcp-flags ALL SYN,FIN,PSH,URG -j Badflags
$IPT -A INPUT -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG -j Badflags
As you can see, for any of the packet matching a rule, we simply have to send the packet to the "Badflags" chain. If we were not using user-defined chains, the first statement would look like:
$IPT -A INPUT -p tcp --tcp-flags ACK,FIN FIN -m limit --limit 10/minute -j LOG --log-prefix "Badflags: "
$IPT -A INPUT -p tcp --tcp-flags ACK,FIN FIN -j DROP
So this would be twice as much work :s. Now, imagine you want to change the target from DROP to REJECT, you simply have to edit 1 line instead of 11
Those badflags rules are well known combinations of illegitimated TCP flags settings. Normal application should not use those, this is why we can DROP those packets safely.
Now, we are going to allow only a small set of ICMP packets. In our example, we want our firewall to be able to receive information such as Timeout (type 11), Host unreachable (type 3) and we want it to reply to pings (type 8 ) and get replies to ping initiated from our firewall (type 0).
In order to do this, we ACCEPT any ICMP packets which contains one of the following code type and then pass all other ICMP packets code to our Firewall chain.
# Accept certain icmp message, drop the others
# and log them through the Firewall chain
# 0 => echo reply
$IPT -A INPUT -p icmp --icmp-type 0 -j ACCEPT
# 3 => Destination Unreachable
$IPT -A INPUT -p icmp --icmp-type 3 -j ACCEPT
# 11 => Time Exceeded
$IPT -A INPUT -p icmp --icmp-type 11 -j ACCEPT
# 8 => Echo
# avoid ping flood
$IPT -A INPUT -p icmp --icmp-type 8 -m limit --limit 1/second -j ACCEPT
$IPT -A INPUT -p icmp -j Firewall
Note the -m limit --limit 1/second, by doing such, our firewall is going to reply to only 1 ping per second, any other ping will be logged (up to 10/min and then DROPped) through the Firewall chain
3.5. Traffic from the Internet
After we have dealt with not well formed packets and icmp packets, we should apply some global rules to streams coming from the outside (remember that our default policy for OUTPUT packets is ACCEPT, so we don't have to allow those).
The basic idea here is to only allow streams that are related to a previous connection (useful for FTP for instance) or already established.
But, we are going to make one exception for SSH because we want to be able to connect to our box from the outside.
We achieve this by accepting any ssh packets from the outside and then only connections in state RELATED or ESTABLISHED
# Accept ssh connections from the Internet
$IPT -A INPUT -i $WAN -p tcp --dport 22 -j ACCEPT
# or only accept from a certain ip
#$IPT -A INPUT -i $WAN -s 18.104.22.168 -p tcp --dport 22 -j ACCEPT
# Accept related and established connections
$IPT -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
Then we are going to DROP silently netbios scan from the outside:
# Drop netbios from the outside, no log, just drop
$IPT -A INPUT -p udp --sport 137 --dport 137 -j DROP
And finally, REJECT any other packet through our user-defined chain Rejectwall:
$IPT -A INPUT -j Rejectwall