This forum is in archive mode. You will not be able to post new content.

Author Topic: [Tutorial] Hardening your Linux box with iptables  (Read 10020 times)

0 Members and 1 Guest are viewing this topic.

Offline 0poitr

  • Peasant
  • *
  • Posts: 149
  • Cookies: 64
    • View Profile
[Tutorial] Hardening your Linux box with iptables
« on: March 01, 2013, 04:38:43 PM »
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::
 
Code: [Select]
       -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
   
Code: [Select]
iptables -t filter -F   
    Create the new chains with
   
Code: [Select]
    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:
Code: [Select]
-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:
Code: [Select]
-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:
Code: [Select]
-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.
   
Code: [Select]
    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.
   
Code: [Select]
    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 :
   
Code: [Select]
    iptables -t filter -A bad_tcp_packets -p tcp ! --dport [port_range] --syn -m conntrack --ctstate NEW -j DROP
   
    Or,
   
Code: [Select]
    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
   
Code: [Select]
    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.
   
Code: [Select]
    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.
   
Code: [Select]
    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.
   
Code: [Select]
    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.
   
Code: [Select]
    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.
   
Code: [Select]
    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 :P
   
Code: [Select]
    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.

   
Code: [Select]
    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:
   
Code: [Select]
    iptables -t filter -A tcp_sieve -p tcp --dport 20:21 -j ACCEPT
   
    Similarly, to allow SSH traffic:
   
Code: [Select]
    iptables -t filter -A tcp_sieve -p tcp --dport 22 -j ACCEPT
   

    Lasty, we return to the calling chain
   
Code: [Select]
    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.

   
Code: [Select]
    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:
   
Code: [Select]
    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.

   
Code: [Select]
    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.

   
Code: [Select]
   -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.

 
Code: [Select]
  -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 :P )
  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
 
Code: [Select]
  iptables-save > file_name
 

  Later restore it with
 
Code: [Select]
  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:
 
   
Code: [Select]
    [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
   
Code: [Select]
    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. :P

Resources:
:
« Last Edit: March 09, 2013, 08:11:45 AM by 0poitr »
Imagination is the first step towards Creation.

Offline geXXos

  • Royal Highness
  • ****
  • Posts: 646
  • Cookies: 178
    • View Profile
Re: [Tutorial] Hardening your Linux box with iptables
« Reply #1 on: March 01, 2013, 05:00:18 PM »
You done a great job here, this is a very nice tut, maybe a candidate for the 1st issue of our mag. rep +1

Offline lucid

  • #Underground
  • Titan
  • **
  • Posts: 2683
  • Cookies: 243
  • psychonaut
    • View Profile
Re: [Tutorial] Hardening your Linux box with iptables
« Reply #2 on: March 01, 2013, 06:03:37 PM »
Oh god, thank you. I've been meaning to learn about this but haven't the time. +1
"Hacking is at least as much about ideas as about computers and technology. We use our skills to open doors that should never have been shut. We open these doors not only for our own benefit but for the benefit of others, too." - Brian the Hacker

Quote
15:04  @Phage : I'm bored of Python

k0f33

  • Guest
Re: [Tutorial] Hardening your Linux box with iptables
« Reply #3 on: March 30, 2014, 04:35:32 AM »
Very good tutorial, thank you.

k0f33

Offline Teapot

  • Peasant
  • *
  • Posts: 127
  • Cookies: -2
  • E-Book Whore
    • View Profile
Re: [Tutorial] Hardening your Linux box with iptables
« Reply #4 on: March 30, 2014, 05:02:12 AM »
very very nice, i am new to linux and still learning her ins and outs.
+1

 



Want to be here? Contact Ande, Factionwars or Kulverstukas on the forum or at IRC.