In the previous chapter we learned how to perform basic input filtering. We even created some rules to filter incoming packets and drop those that were deemed invalid. While this protects us from incoming packets it does nothing to protect the rest of the Internet from us. We can solve this very easily by adding a similar chain to that which we created to filter incoming TCP packets. The only difference is that this time we shall connect it to the OUTPUT chain.
iptables -N TCP-OUT-FILTER
iptables -A TCP-OUT-FILTER -j TCPCHECK
iptables -A OUTPUT -p tcp -j TCP-OUT-FILTER
Add the above lines of code to the end of the script and execute it as usual. Now when you display the Netfilter status you should see output similar to that given below.
Chain INPUT (policy ACCEPT 42582 packets, 5132K bytes)
pkts bytes target prot opt in out source destination
23 1484 TCP-IN-FILTER tcp -- any any anywhere anywhere
Chain OUTPUT (policy ACCEPT 7808 packets, 2021K bytes)
pkts bytes target prot opt in out source destination
17 3444 TCP-OUT-FILTER tcp -- any any anywhere anywhere
Chain LOGDROP (1 references)
pkts bytes target prot opt in out source destination
0 0 LOG all -- any any anywhere anywhere LOG level warning prefix `FIREWALL - DROP:'
0 0 DROP all -- any any anywhere anywhere
Chain TCP-IN-FILTER (1 references)
pkts bytes target prot opt in out source destination
23 1484 TCPCHECK all -- any any anywhere anywhere
Chain TCP-OUT-FILTER (1 references)
pkts bytes target prot opt in out source destination
17 3444 TCPCHECK all -- any any anywhere anywhere
Chain TCPCHECK (2 references)
pkts bytes target prot opt in out source destination
0 0 LOGDROP tcp -- any any anywhere anywhere tcp flags:!FIN,SYN,RST,ACK/SYN state NEW
As before we can see that packets are being routed to the TCP-OUT-FILTER chain by examining the pkts column. It should be noted at this point that the number given here is the number of packets which were matched by this rule, not the number of packets which have been inspected.
So far we have concentrated on what kind of packets we don't want coming in to or leaving our system. This is a good start but we are no closer to being able to set the INPUT and OUTPUT chain's policies to DROP as we have no rules to ACCEPT any packets.
Before we can start adding rules to our chains we need to lay some foundations so that we can keep our rules organised in a logical fashion. To achieve this we shall create four new chains called TCP-IN-REQ, for incoming requests, TCP-OUT-RESP, for outgoing responses, TCP-OUT-REQ, for outgoing requests, and finally TCP-IN-RESP for their incoming responses. This should allow us to more easily keep track of what we are allowing and in which direction.
Add the following lines to the script, between the lines shown in grey, so that they come before the creation of the TCP-IN-FILTER chain but after we have added the last rule to the TCPCHECK chain.
iptables -N TCPCHECK
iptables -A TCPCHECK -p tcp ! --syn -m state --state NEW -j LOGDROP
iptables -A TCPCHECK -m state --state INVALID -j LOGDROP
iptables -N TCP-IN-REQ
iptables -N TCP-OUT-RESP
iptables -N TCP-OUT-REQ
iptables -N TCP-IN-RESP
iptables -N TCP-IN-FILTER
iptables -A TCP-IN-FILTER -j TCPCHECK
We can now add the lines shown below to hook-up the chains we have just created to the TCP-IN-FILTER and TCP-OUT-FILTER chains which we created earlier. Again, existing lines are shown in grey.
iptables -N TCP-IN-FILTER
iptables -A TCP-IN-FILTER -j TCPCHECK
iptables -A TCP-IN-FILTER -j TCP-IN-REQ
iptables -A TCP-IN-FILTER -j TCP-IN-RESP
iptables -A INPUT -p tcp -j TCP-IN-FILTER
iptables -N TCP-OUT-FILTER
iptables -A TCP-OUT-FILTER -j TCPCHECK
iptables -A TCP-OUT-FILTER -j TCP-OUT-RESP
iptables -A TCP-OUT-FILTER -j TCP-OUT-REQ
iptables -A OUTPUT -p tcp -j TCP-OUT-FILTER
All packets entering or leaving the system will now traverse not just the TCPCHECK chain but, assuming they were not already dropped, they will also traverse the appropriate request and response chains, depending on their direction.
You can check that everything is working by comparing the relevant lines of the Netfilter status with the example given below. As before we have omitted irrelevant output.
Chain INPUT (policy ACCEPT 44186 packets, 5286K bytes)
pkts bytes target prot opt in out source destination
13 964 TCP-IN-FILTER tcp -- any any anywhere anywhere
Chain OUTPUT (policy ACCEPT 8516 packets, 2108K bytes)
pkts bytes target prot opt in out source destination
7 924 TCP-OUT-FILTER tcp -- any any anywhere anywhere
Chain TCP-IN-FILTER (1 references)
pkts bytes target prot opt in out source destination
13 964 TCPCHECK all -- any any anywhere anywhere
13 964 TCP-IN-REQ all -- any any anywhere anywhere
13 964 TCP-IN-RESP all -- any any anywhere anywhere
Chain TCP-IN-REQ (1 references)
pkts bytes target prot opt in out source destination
Chain TCP-IN-RESP (1 references)
pkts bytes target prot opt in out source destination
Chain TCP-OUT-FILTER (1 references)
pkts bytes target prot opt in out source destination
7 924 TCPCHECK all -- any any anywhere anywhere
7 924 TCP-OUT-RESP all -- any any anywhere anywhere
7 924 TCP-OUT-REQ all -- any any anywhere anywhere
Chain TCP-OUT-REQ (1 references)
pkts bytes target prot opt in out source destination
Chain TCP-OUT-RESP (1 references)
pkts bytes target prot opt in out source destination
Now that we have the infrastructure in place we can start to build the rules which will ACCEPT traffic. In this section we shall only cover simple services which use a single port for communications such as HTTP and SSH. We shall cover complex services which use multiple ports, such as FTP, in the next section.
As HTTP is probably the most widely used protocol on the Internet let's start by allowing access to an HTTP server running on the local machine. We can accomplish this by adding the following lines to our firewall configuration script.
iptables -N TCP-OUT-REQ
iptables -N TCP-IN-RESP
iptables -A TCP-IN-REQ -p tcp --dport http -m state --state NEW -j ACCEPT
iptables -A TCP-IN-REQ -p tcp --dport http -m state --state ESTABLISHED -j ACCEPT
iptables -A TCP-OUT-RESP -p tcp --sport http -m state --state ESTABLISHED -j ACCEPT
iptables -N TCP-IN-FILTER
iptables -A TCP-IN-FILTER -j TCPCHECK
The above three rules allow new incoming request packets, packets which are part of an established incoming connection, and packets which are part of the already established response connection respectively. The first two rules use the dport sub-matcher of the protocol matcher to ensure that the request is destined for the HTTP port on the local machine. The third rule uses the sport sub-matcher to ensure that the outgoing response is coming from the local HTTP server. As you can see most commonly used protocols are known to the sub-matchers by name. For less well known protocols a port number can be specified. All the rules also use the state matcher which we met earlier.
If we want to allow the local machine to access web servers hosted on other machines, and we probably do, then we can use the rules given below. Add them to the script below the rules which we inserted earlier.
iptables -A TCP-OUT-REQ -p tcp --dport http -m state --state NEW -j ACCEPT
iptables -A TCP-OUT-REQ -p tcp --dport http -m state --state ESTABLISHED -j ACCEPT
iptables -A TCP-IN-RESP -p tcp --sport http -m state --state ESTABLISHED -j ACCEPT
As you can see the rules are the same for both the client-side and the server-side, the only difference is which chain they are added to. In this case the first two rules represent the request, so they are added to the outgoing request chain, while the third rule represents the response, so is added to the incoming response chain.
We mentioned earlier that some services, such as FTP, use more complex protocols which require the use of multiple related connections. In the case of the FTP protocol a separate connection is used for the transfer of data. The port number for this second connection can vary widely. It can either be the default data port, which for FTP is port 20, it can specified during the negotiation either by the requesting host, in which case the server will open a connection to the specified port, or by the server, in which case the client will open a connection to the specified port. This is clearly going to require a different approach to that used for the simple protocols above.
Lets continue with the FTP example we have started by adding the following rules to the firewall configuration script just below the ones we added earlier. These rules will allow access to the control port of the FTP server and hence are exactly the same as those used for simple servers.
iptables -A TCP-IN-REQ -p tcp --dport ftp -m state --state NEW -j ACCEPT
iptables -A TCP-IN-REQ -p tcp --dport ftp -m state --state ESTABLISHED -j ACCEPT
iptables -A TCP-OUT-RESP -p tcp --sport ftp -m state --state ESTABLISHED -j ACCEPT
Now that our clients can connect to the control port of our FTP server we need to address the problem of the data port. If we knew that data was always requested on our data port we could simply open the data port to all connections in the same way that we would open any other port. Unfortunately for us the FTP protocol is more complex than that and requires the client to open the data connection from its data port to a port which the server specifies. Knowing this we could decide to open all ports on our server to connections which come from the FTP data port but this would be foolish as anyone could use this to bypass our firewall altogether, it also wouldn't address the problem of passive transfers. What we really need to be able to do is watch the FTP control port and see which ports are going to be used and then open just those.
For that reason the Netfilter developers created the conntrack interface and related modules. One of these modules provides us with exactly the functionality which we require here, in this case the FTP conntrack module. There are other modules available for various protocols including, but by no means limited to, SCTP, TFTP and PPTP.
Use of the conntrack subsystem is very simple. In fact we have already been using it! Every time we have used the state matcher we have been making use of the connection tracking information maintained by the Netfilter connection tracking sub-system. In addition to the basic information which we have been exploiting the various conntrack modules also keep track of the ports which have been specified for use during conversations on the control connections of their related protocols.
Several matchers are capable of making use of this information to provide additional functionality which allows rules to be written to act on the associated packets accordingly. The most used of these is the RELATED state match. It can be used in place of the NEW state to allow access to ports required for related communication channels. Another useful matcher, which we shall also be using here, is the helper matcher which is used to determine which of the conntrack extension modules identified this as related traffic.
Now that we know how to access the state information maintained by the conntrack subsystem and the FTP conntrack module, we can create some appropriate rules to add to our script.
iptables -A TCP-OUT-RESP -m helper --helper ftp -m state --state RELATED -j ACCEPT
iptables -A TCP-OUT-RESP -m helper --helper ftp -m state --state ESTABLISHED -j ACCEPT
iptables -A TCP-IN-RESP -m helper --helper ftp -m state --state RELATED -j ACCEPT
iptables -A TCP-IN-RESP -m helper --helper ftp -m state --state ESTABLISHED -j ACCEPT
The first pair of rules, shown above, is used for the case where the server is opening the connection to a data port specified by the client. The second pair of rules covers the case where the client is opening a connection to a port specified by the server. We need both of these pairs of rules to allow for both active and passive transfers respectively. You should also note that the RELATED state is essentially the same as the NEW state in that it only matches the first packet on a connection. If the ESTABLISHED matches were omitted from the above code only one packet would make it through the firewall.
If you wish to be able to open FTP control connections from the local machine to remote servers then you will need to add the control port rules above again with the direction part of the chain specifier reversed in the same way as we did for the HTTP protocol earlier. For your convenience the necessary code is given below.
iptables -A TCP-OUT-REQ -p tcp --dport ftp -m state --state NEW -j ACCEPT
iptables -A TCP-OUT-REQ -p tcp --dport ftp -m state --state ESTABLISHED -j ACCEPT
iptables -A TCP-IN-RESP -p tcp --sport ftp -m state --state ESTABLISHED -j ACCEPT
You should now be able to add similar entries to the configuration script to allow access to all the other TCP based protocols which you use.