iptables/netfilter is the de-facto framework in Linux to do all kinds of manipulations on network traffic/packets. It's a versatile tool and can be used to bulid firewall, nat box, dmz, pretty much anything you want to do with network traffic on your interfaces.
In this tutorial, we'll concentrate on building a firewall with iptables from scratch. Our scenario is a single box connected to the internet through a gateway, with no traffic to nat.
Things you'll need:: 1.
A linux box with iptables installed 2.
root account 3.
Basic knowledge of the tcp header structure will be helpful. ( See:
http://www.frozentux.net/iptables-tutorial/iptables-tutorial.html#TCPHEADERS )
Before we dive into the intricacies of building a firewall, I'll explain the basic ideas of how iptables functions and the command-line options briefly which should be enough for understanding commands used in this tutorial.
Basic Idea:
iptables has a set of tables. The tables further contain a set of pre-defined chains. These chains contain the rule/s that define what to do with a packet.
Presenty, the tables available with iptables are ::
1.
filter 2.
nat 3.
mangle 4.
raw 5.
security The only one we'll use here is the filter table.
The filter table contains these pre-defined chains ::
1.
INPUT ( for packets destined to local sockets )
2.
FORWARD ( for packets being routed through the box, mainly for nat-ing purposes )
3.
OUTPUT ( for locally generated outbound packets )
More chains can be created in a table using command line options. But the pre-defined ones will always be there.
Let's describe the whole thing with an analogy. Guess we have a multi-racked bookshelf. Each rack has multiple compartments which group books of a similar kind. So, the racks can be thought of as the tables (in iptables), the compartments as the chains and the books as the rules themselves.
A rule can be devided in two parts :
-t [table_name] -A [chain_name]
[Match criteria] [Target] Match criteria describes the packet properties to look for.
Target on the other hand describes what to do if a match was found.
Build-in targets ( only the ones we'll need here ):
ACCEPT,
DROP, REJECT,
RETURN The target names are self-descriptive. The only thing that needs an explanation is: DROP just ignores the packet and drops it, the sender gets no respose.
Whereas, REJECT actively refuses the connection, usually by replying with a icmp-port-unreachable.
Targets can also be user-defined chains (udc). RETURN returns from a udc to where it was called from.
For a given packet, iptables will traverse the rulesets as long as it hasn't found a match that defines its fate as any of ACCEPT, DROP or REJECT. If none of them match, the policy for the present chain ( each pre-defined chain has a default policy ) is applied. Basic Usage:: -t --table table_name
Select one of the tables.
-A, --append chain rule-specification
Append one or more rules to the end of the selected chain.
-L, --list [chain]
List all rules in the selected chain. If no chain is selected, all chains are listed. Like every other iptables command, it applies to the
specified table (filter is the default), so NAT rules get listed by
-S, --list-rules [chain]
List rules directly as the commands that are fed to iptables.
-F, --flush [chain]
Flush the selected chain (all the chains in the table if none is given). This is equivalent to deleting all the rules one by one.
-N, --new-chain chain
Create a new user-defined chain by the given name. There must be no target of that name already.
-P --policy
Set the default target of pre-defined chains. When none of the present rules match the packet being processed, the policy determines its fate.
-p, --protocol protocol
The protocol of the rule or of the packet to check. The specified protocol can be one of tcp, udp, udplite, icmp, esp, ah, sctp or the spe‐
cial keyword "all", or it can be a numeric value.
-s, --source address[/mask][,...]
Source specification. Address can be either a network name, a hostname, a network IP address (with /mask), or a plain IP address.
-d, --destination address[/mask][,...]
Destination specification.
-j, --jump target
This specifies the target of the rule; i.e., what to do if the packet matches it. The target can be a user-defined chain (other than the
one this rule is in), one of the special builtin targets which decide the fate of the packet immediately.
-i, --in-interface name
Name of an interface via which a packet was received (only for packets entering the INPUT, FORWARD and PREROUTING chains).
-o, --out-interface name
Name of an interface via which a packet is going to be sent (for packets entering the FORWARD, OUTPUT and POSTROUTING chains).
-f, --fragment
This means that the rule only refers to second and further fragments of fragmented packets.
-m module-name
Load an external module. iptables can use external modules to do packet filtering/matching. These modules are loaded dynamically if your
kernel supports that. Otherwise, you'll have to load them manually wih modprobe.
Now that we have enough information on the usage, we'll start building the firewall, one rule at a time.
:: Building the Firewall :: (Inbound traffic) Before bulding any firewall we have to consider what we services we want to allow in/out and what not. The firewall to be built in this tutorial will allow no incoming services and allow outbound traffic generated only by a few trusted users.
We'll block inbound traffic to all ports and then open up if any is needed. We'll also log and drop packets which show properties of typical port scans.
The rules for all the inbound filtering will (obviously) reside in the filter table.
To separate the rules into managable sections, we'll create four new chains :
bad_packets ( drop all the bad/invalid packets )
bad_tcp_packets ( most of the scan thwarting is done here )
tcp_sieve ( add any service is to be allowed here )
udp_packets ( filter udp packets )
icmp_packets ( filter icmp packets ) These chains will be called from within the INPUT chain.
This is how the chain tree would look :
filter
|
|
|\_ INPUT
|
|\_ bad_packets
|
|\_ bad_tcp_packets
|
|\_ udp_packets
|
|\_ icmp_packets
|
|\_ OUTPUT
|
\_ FORWARD ( we are not using it )
The user defined chains will be called from the INPUT chain. The path of an incoming packet through the ruleset will look like this:
Interface -> INPUT -> bad_packets udp_packets -> icmp_packets -> INPUT
\_ bad_tcp_packets _/ I've shown call to the bad_tcp_packets in a different way to show the fact that, it is called from the bad_packets chain, not from INPUT.
Note that, for a particular packet, the chain can break at any point if it's ACCEPTed/DROPed/REJECTed. Packets traveling all the way back to INPUT will be treated with policy of the INPUT chain.
For an outgoing packet, it'd be rather simple :
OUTPUT -> interface So, lets begin~~
First, the present iptables chains will be cleared/flushed with
iptables -t filter -F
Create the new chains with
iptables -t filter -N bad_packets
iptables -t filter -N bad_tcp_packets
iptables -t filter -N tcp_sieve
iptables -t filter -N udp_packets
iptables -t filter -N icmp_packets
Throughout the tutorial, we'll use some iptables extensions. While I'll describe what the additional options mean, I highly suggest reading the detail of the extension from iptables-extensions(8 ).
We'll use ::
the
conntrack extension to determine the state of a connection. Connection states we'll use here :
NEW ( a packet belonging to a connection which has seen traffic only in one direction )
ESTABLISHED ( belongs to a conn. which has seen traffic in both direction )
RELATED ( a new connection, but associated with an already establihed connection )
INVALID ( a packet associated with no known conn. )
syntax:
-m conntrack --ctstate [state]
the
limit extension to limit what we do if a matching packet is found.
See
http://www.netfilter.org/documentation/HOWTO//packet-filtering-HOWTO-7.html#ss7.2 under 'limit' for a wonderfull description of what it does.
syntax:
-m limit --limit [limit] --limit-burst [burst]
the
owner extension to allow only a few trusted users to make outbound connections. The owners are specified by either uid or their usernames.
syntax:
-m owner --uid-owner [uid/uname]
Other than these, specifying -p [protocol] also loads the corresponding extensions. For example, -p tcp loads the tcp extension to do the matching.
Building the chains: :: bad_packets :: We log and drop all the invalid packets here. Call the bad_tcp_packets chain if it's not invalid. If the packet still survives, return to the INPUT chain.
iptables -t filter -A bad_packets -m conntrack --ctstate INVALID -j LOG --log-prefix "[Dropped] Invalid packed:"
iptables -t filter -A bad_packets -m conntrack --ctstate INVALID -j DROP
iptables -t filter -A bad_packets -p tcp -j bad_tcp_packets
iptables -t filter -A bad_packets -j RETURN
:: bad_tcp_packets :: We log and drop all the scan probes here.
I looked into the nmap(1) to know what packets are sent to initiate different tcp scans and created the rulesets accordingly.
a. The very common SYN scan. Only the SYN flag is set. FIN, RST, ACK is cleared. Sending a SYN packet (i.e. tcp packet with SYN set) is the default way to initiate any tcp connection. So, don't add these if you are to allow incoming traffic to any services.
iptables -t filter -A bad_tcp_packets -p tcp --syn -m conntrack --ctstate NEW -m limit --limit 1/sec -j LOG --log-prefix "[Drop]SYN scan: "
iptables -t filter -A bad_tcp_packets -p tcp --syn -m conntrack --ctstate NEW -j DROP
In case, you want to drop all SYN packets except for a few ports or from a few trusted sources you can do this instead :
iptables -t filter -A bad_tcp_packets -p tcp ! --dport [port_range] --syn -m conntrack --ctstate NEW -j DROP
Or,
iptables -t filter -A bad_tcp_packets -p tcp ! -s [source_ip] --syn -m conntrack --ctstate NEW -j DROP
Or both.
The '!' before --dport and -s tells iptables to match all but those. Port range are in the form start_port:end_port.
--syn is equivalet to --tcp-flags FIN,SYN,RST,ACK SYN
It tells iptables which flags to look for ( FIN,SYN,RST,ACK ) and which one should be set ( SYN ) in the tcp header, which also implies that FIN,ACK,RST should be unset. We use 'limit' to limit log entries.
b. Drop packets which don't have SYN set, but still are new connections
iptables -t filter -A bad_tcp_packets -p tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j DROP
c. Drop ACK scan probes.
iptables -t filter -A bad_tcp_packets -p tcp --tcp-flags FIN,SYN,RST,ACK ACK -m conntrack --ctstate NEW -m limit --limit 1/sec -j LOG --log-prefix "[Drop]ACK scan: "
iptables -t filter -A bad_tcp_packets -p tcp --tcp-flags FIN,SYN,RST,ACK ACK -m conntrack --ctstate NEW -j DROP
d. Drop Null scan probes.
iptables -t filter -A bad_tcp_packets -p tcp --tcp-flags ALL NONE -m conntrack --ctstate NEW -m limit --limit 1/sec -j LOG --log-prefix "[Drop]Null scan: "
iptables -t filter -A bad_tcp_packets -p tcp --tcp-flags ALL NONE -m conntrack --ctstate NEW -j DROP
d. Drop FIN scan probes.
iptables -t filter -A bad_tcp_packets -p tcp --tcp-flags ALL FIN -m conntrack --ctstate NEW -m limit --limit 1/sec -j LOG --log-prefix "[Drop]FIN scan: "
iptables -t filter -A bad_tcp_packets -p tcp --tcp-flags ALL FIN -m conntrack --ctstate NEW -j DROP
d. Drop XMAS scan probes.
iptables -t filter -A bad_tcp_packets -p tcp --tcp-flags ALL FIN,PSH,URG -m conntrack --ctstate NEW -m limit --limit 1/sec -j LOG --log-prefix "[Drop]XMAS scan: "
iptables -t filter -A bad_tcp_packets -p tcp --tcp-flags ALL FIN,PSH,URG -m conntrack --ctstate NEW -j DROP
d. Drop Maimon scan probes.
iptables -t filter -A bad_tcp_packets -p tcp --tcp-flags ALL FIN,ACK -m conntrack --ctstate NEW -m limit --limit 1/sec -j LOG --log-prefix "[Drop]Maimon scan: "
iptables -t filter -A bad_tcp_packets -p tcp --tcp-flags ALL FIN,ACK -m conntrack --ctstate NEW -j DROP
return to the calling chain if the packet survided
iptables -t filter -A bad_tcp_packets -p tcp -j RETURN
:: tcp_sieve :: I created this chain to do additional processing on tcp packets.
The only rule I have in this chain is to reject Ident probes on port 113. It was a dilemma whether to drop the ident probes or to reject/allow them.
Ident probes are sent by IRC servers for host authentication when you try to connect to them. (
http://www.grc.com/port_113.htm )
In my case, if I dropped the packets, connecting to the freenode servers hanged for about half a minute before allowing me in. If I rejected the packets, everything went as usual. So I decided to finally go with rejecting.
iptables -t filter -A tcp_sieve -p tcp --dport 113 -m limit --limit 1/sec --limit-burst 3 -j REJECT --reject-with icmp-port-unreachable
Notice the limit? Remember that since I'm actively rejecting the connection, the kernel has to send a packet back. That means if someone floods my 113 with ident probes it'll eat up a lot of cpu cycles. Hence, the limit. (other than that i'm a bit paranoid about these things.)
Now add rules to allow any inbound services. For e.g. if you host a ftp server:
iptables -t filter -A tcp_sieve -p tcp --dport 20:21 -j ACCEPT
Similarly, to allow SSH traffic:
iptables -t filter -A tcp_sieve -p tcp --dport 22 -j ACCEPT
Lasty, we return to the calling chain
iptables -t filter -A tcp_sieve -p tcp -j RETURN
:: udp_packets :: If you ever used a windows firewall and took a look at the firewall log, you must have seen lots of noise on ports 135 - 139. These ports are used by windows machines to broadcast packets for netbios based services. We'll drop it all. We'll also reject udp datagrams on port 113.
iptables -t filter -A udp_packets -p udp --dport 135:139 -j DROP
iptables -t filter -A udp_packets -p udp --dport 113 -m limit --limit 1/sec --limit-burst 3 -j REJECT --reject-with icmp-port-unreachable
Here, you can open up a couple of ports if you need. For e.g. ICQ needs port 4000, port 53 if it's a DNS server, port 123 if you use NTP etc.
Return:
iptables -t filter -A udp_packets -p udp -j RETURN
:: icmp_packets :: Our policy would be to drop all icmp packets except only icmp-time-exceeded (code 11). Otherwise, unsuccessfull connections will hang for sometime before finally giving up. We'll also drop any fragmented icmp packets.
iptables -t filter -A icmp_packets -p icmp -f -j DROP
iptables -t filter -A icmp_packets -p icmp --icmp-type 11 -j ACCEPT
iptables -t filter -A icmp_packets -p icmp -j DROP
iptables -t filter -A icmp_packets -p icmp -j RETURN
Now that our chains are set we'll set the default policy of INPUT to DROP. All packets of established or related conns are accepted, also all traffic from the loopback interface will be allowed. Finally we'll add rules to call our created chains. If any packet traverses all chains and back to INPUT, it'll invariably be dropped due to the policy. Before dropping such a packet we'll log it.
-P INPUT DROP
iptables -t filter -A INPUT -i lo -j ACCEPT
iptables -t filter -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -t filter -A INPUT -j bad_packets
iptables -t filter -A INPUT -p tcp -j tcp_sieve
iptables -t filter -A INPUT -p udp -j udp_packets
iptables -t filter -A INPUT -p icmp -j icmp_packets
iptables -t filter -A INPUT -m limit --limit 5/min -j LOG --log-prefix "[Dead]Odd packet: "
(Outbound traffic) As I said before, we're to allow only trusted traffic outwards. As usuall, all traffic from lo is allowed.
The --uid-owner parameter can take either a single username or a range of userids. See /etc/passwd. The uid/s in your case may differ from mine. After all, you've to decide which you want to allow. Any traffic that doesn't get allowed this way will get logged and dropped due to the policy.
-P OUTPUT DROP
iptables -t filter -A OUTPUT -s 127.0.0.1/32 -j ACCEPT
iptables -t filter -A OUTPUT -o lo -j ACCEPT
iptables -t filter -A OUTPUT -o eth0 -m owner --uid-owner 0 -j ACCEPT
iptables -t filter -A OUTPUT -o eth0 -m owner --uid-owner 2 -j ACCEPT
iptables -t filter -A OUTPUT -o eth0 -m owner --uid-owner 8 -j ACCEPT
iptables -t filter -A OUTPUT -o eth0 -m owner --uid-owner 33 -j ACCEPT
iptables -t filter -A OUTPUT -o eth0 -m owner --uid-owner 1000 -j ACCEPT
iptables -t filter -A OUTPUT -o eth0 -m owner --uid-owner 125 -j ACCEPT
iptables -t filter -A OUTPUT -o eth0 -m owner --uid-owner 999 -j ACCEPT
iptables -t filter -A OUTPUT -o eth0 -m limit --limit 1/sec -j LOG --log-prefix "Out pkt dropped: "
-------------------------------------------------------- Hoops! Finally our firewall is complete ( well, firewalls never are complete
)
But we have a few more things to do. To make sure you don't have to run all the commands each time you run iptables, just save the ruleset with
iptables-save > file_name
Later restore it with
iptables-restore < file_name
Note: the default rulesets are generally kept in
/etc/iptables/ directory. You might as well save it there under a different name.
Do it automatically at boot time:
For arch users, if you're using the legacy rc scripts, after saving the ruleset with file_name edit
/etc/conf.d/iptables and change the IPTABLES_CONF value to file_name. Also make sure iptables is in the daemons array in
/etc/rc.conf.
For systemd users, edit the
/usr/lib/systemd/system/iptables.service file. Mine looks like this:
[Unit]
Description=Packet Filtering Framework
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/sbin/iptables-restore <path_to_rules_file>
ExecStop=/usr/lib/systemd/scripts/iptables-flush
[Install]
WantedBy=multi-user.target
Checking the logs:: iptables uses syslog interface to log messages. On arch, the default log file for iptables is /var/log/iptables.log
To log to a different file, change this line in
/etc/syslog-ng/syslog-ng.conf destination d_iptables { file("/var/log/iptables.log"); };
to use the file you want.
On systems that don't have a different log file for iptables, the messages will probably be saved in
/var/log/messages.
Checking your firewall:: Now is the time to check how the firewall fairs. Check if you can make all your necessary connections. If you have another computer on LAN, try port scans, if you have services running check if you can access them.
You can also use nmap to scan localhost. But scanning localhost often produces results which would be different than if scanned from a different machine. Probably because of the fact that many services listen on the loopback interface.
If you don't have the opportunity to scan from an external machine from the internet, use ShieldsUP.
https://www.grc.com/x/ne.dll?bh0bkyd2 To list which services are listening on ethernet interface, run netstat -lpn. Look for the ones that have as proto, ipv4/6.
Running lsof -i -n -P also gives a good overview.
------------------------------------------------ Where to go form here:: iptables is vast. It only takes imagination and some patience to get lost within. See the links in the resources section below.
Lastly, I'm no iptables guru. There may have been ( and probably are ) mistakes in the tut. Kindly point those out. If you can't understand something or, if you need specific mods regarding a rule, ask me.
I'll try my best to answer.
P.S. Because I wrote the entire tutorial in vim, and I have smartindent on, there's a bit of problem with the text formatting.
Resources::