Take the 2-minute tour ×
Server Fault is a question and answer site for professional system and network administrators. It's 100% free, no registration required.

I have been trying to put together a basic server iptables script that will work for most sites just running a basic webserver using HTTP(S) and SSH (ports 80, 443, & 22). After all, most VPS only need these starting ports rules and can add mail or game ports later as need.

So far I have the following ruleset and I was wondering if anyone knows of a better script or any improvements that could be added.

*filter

#  Allows all loopback (lo0) traffic and drop all traffic to 127/8 that doesn't use lo0
-A INPUT -i lo -j ACCEPT
-A INPUT ! -i lo -d 127.0.0.0/8 -j REJECT

#  Accepts all established inbound connections
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

#  Allows all outbound traffic
#  You can modify this to only allow certain traffic
-A OUTPUT -j ACCEPT

# Allows HTTP and HTTPS connections from anywhere (the normal ports for websites)
-A INPUT -p tcp --dport 80 -j ACCEPT
-A INPUT -p tcp --dport 443 -j ACCEPT

#  Allows SSH connections (only 4 attempts by an IP every 3 minutes, drop the rest)
-A INPUT -p tcp -m tcp --dport 22 -m state --state NEW -m recent --set --name DEFAULT --rsource
-A INPUT -p tcp -m tcp --dport 22 -m state --state NEW -m recent --update --seconds 180 --hitcount 4 --name DEFAULT --rsource -j DROP
-A INPUT -p tcp -m state --state NEW --dport 22 -j ACCEPT

# Allow ping
-A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT

# log iptables denied calls
-A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables denied: " --log-level 7

# Reject all other inbound - default deny unless explicitly allowed policy
-A INPUT -j REJECT
-A FORWARD -j REJECT

COMMIT

iptables is one of the most important parts of securing your box (also see fail2ban) and yet there are many people like myself that have trouble understanding everything that goes into making a secure basic firewall for our servers.

What is the most secure way to only open the basic ports needed for a webserver?

Update: cyberciti.biz has another iptables script that looks pretty good.

Also, rather than using Denyhosts or fail2ban you could use iptables itself to block bad repeated attempts at SSH.

share|improve this question
    
You say it's a VPS. I assume it has a LAN IP as well right? Do you trust every machine on your subnet? How paranoid do you want to be here, you can use OUTPUT filtering to further secure your machine. Let me know and I can respond with what I'd suggest you use. –  hobodave Sep 25 '10 at 18:55
    
Good point, given that most VPS are in a VM with others possibly accessible from LAN I would say that not-trusting them would be the smart starting place. If you have additional VPS then you could add rules later to access them (i.e. webserver to database). –  Xeoncross Sep 25 '10 at 19:22
add comment

5 Answers

up vote 6 down vote accepted
+50

The most secure way to work with iptables is close everything and only open what you need. I'm kind of distracted, so I always try to be as lazy as possible, so I do not make mistakes which can lead the server to be unsecure.

I use this one, only a little bit of varible assignment must be done in order to make it work.

  #!/bin/bash +x

  # first author: marcos de vera
  # second: joan marc riera

  ip=/sbin/iptables
  mriera="xx.xx.xx.xx"
  nsancho="yy.yy.yy.yy"
  admins="$mriera $nsancho "
  sshers=""
  mysqlrs="zz.zz.zz.zz/23"
  tcpservices="80 443 22"
  udpservices=""

  # Firewall script for servername

  echo -n ">> Applying iptables rules... "

  ## flushing...
  $ip -F
  $ip -X
  $ip -Z
  $ip -t nat -F

  # default: DROP!
  $ip -P INPUT DROP
  $ip -P OUTPUT DROP
  $ip -P FORWARD DROP

  # filtering...

  # localhost: free pass!
  $ip -A INPUT -i lo -j ACCEPT
  $ip -A OUTPUT -o lo -j ACCEPT

  # administration ips: free pass!
  for admin in $admins ; do
      $ip -A INPUT -s $admin -j ACCEPT
      $ip -A OUTPUT -d $admin -j ACCEPT
  done

  # allow ssh access to sshers
  for ssher in $sshers ; do
      $ip -A INPUT -s $ssher -p tcp -m tcp --dport 22 -j ACCEPT
      $ip -A OUTPUT -d $ssher -p tcp -m tcp --sport 22 -j ACCEPT
  done

  # allow access to mysql port to iReport on sugar

  for mysql in $mysqlrs ; do
      $ip -A INPUT -s $mysql -p tcp -m tcp --dport 3306 -j ACCEPT
      $ip -A OUTPUT -d $mysql -p tcp -m tcp --sport 3306 -j ACCEPT
      $ip -A INPUT -s $mysql -p udp -m udp --dport 3306 -j ACCEPT
      $ip -A OUTPUT -d $mysql -p udp -m udp --sport 3306 -j ACCEPT
  done


  # allowed services
  for service in $tcpservices ; do
      $ip -A INPUT -p tcp -m tcp --dport $service -j ACCEPT
      $ip -A OUTPUT -p tcp -m tcp --sport $service -m state --state RELATED,ESTABLISHED -j ACCEPT
  done
  for service in $udpservices ; do
      $ip -A INPUT -p udp -m udp --dport $service -j ACCEPT
      $ip -A OUTPUT -p udp -m udp --sport $service -m state --state RELATED,ESTABLISHED -j ACCEPT
  done

  $ip -A INPUT -j LOG --log-level 4
  # VAS and VGP
  #88 tcp udp
  #389 tcp ldap queries , udp ldap ping
  #464 tcp upd kerberos
  #3268 tcp global catalog access
  for dc in ip.ip.ip.ip ; do # our dc servers for some ldap auth
      vas=88
      $ip -A INPUT -s $dc -p tcp -m tcp --dport $vas -j ACCEPT
      $ip -A OUTPUT -d $dc -p tcp -m tcp --dport $vas -j ACCEPT
      $ip -A INPUT -s $dc -p udp -m udp --dport $vas -j ACCEPT
      $ip -A OUTPUT -d $dc -p udp -m udp --dport $vas -j ACCEPT
      ldap=389
      $ip -A INPUT -s $dc -p tcp -m tcp --dport $ldap -j ACCEPT
      $ip -A OUTPUT -d $dc -p tcp -m tcp --dport $ldap -j ACCEPT
      $ip -A INPUT -s $dc -p udp -m udp --dport $ldap -j ACCEPT
      $ip -A OUTPUT -d $dc -p udp -m udp --dport $ldap -j ACCEPT
      kpasswd=464
      $ip -A INPUT -s $dc -p tcp -m tcp --dport $kpasswd -j ACCEPT
      $ip -A OUTPUT -d $dc -p tcp -m tcp --dport $kpasswd -j ACCEPT
      $ip -A INPUT -s $dc -p udp -m udp --dport $kpasswd -j ACCEPT
      $ip -A OUTPUT -d $dc -p udp -m udp --dport $kpasswd -j ACCEPT
      gca=3268
      $ip -A INPUT -s $dc -p tcp -m tcp --dport $gca -j ACCEPT
      $ip -A OUTPUT -d $dc -p tcp -m tcp --dport $gca -j ACCEPT
      vgp=445
      $ip -A INPUT -s $dc -p tcp -m tcp --dport $vgp -j ACCEPT
      $ip -A OUTPUT -d $dc -p tcp -m tcp --dport $vgp -j ACCEPT
  done


  # allow the machine to browse the internet
  $ip -A INPUT -p tcp -m tcp --sport 80 -m state --state RELATED,ESTABLISHED -j ACCEPT
  $ip -A OUTPUT -p tcp -m tcp --dport 80 -j ACCEPT
  $ip -A INPUT -p tcp -m tcp --sport 443 -m state --state RELATED,ESTABLISHED -j ACCEPT
  $ip -A OUTPUT -p tcp -m tcp --dport 443 -j ACCEPT

  $ip -A INPUT -p tcp -m tcp --sport 8080 -m state --state RELATED,ESTABLISHED -j ACCEPT
  $ip -A OUTPUT -p tcp -m tcp --dport 8080 -j ACCEPT


  # don't forget the dns...
  $ip -A INPUT -p udp -m udp --sport 53 -j ACCEPT
  $ip -A OUTPUT -p udp -m udp --dport 53 -j ACCEPT

  # ... neither the ntp... (hora.rediris.es)
  #$ip -A INPUT -s 130.206.3.166 -p udp -m udp --dport 123 -j ACCEPT
  #$ip -A OUTPUT -d 130.206.3.166 -p udp -m udp --sport 123 -j ACCEPT

  $ip -A INPUT -p udp -m udp --dport 123 -j ACCEPT
  $ip -A OUTPUT -p udp -m udp --sport 123 -j ACCEPT


  # and last but not least, the smtp access
  $ip -A INPUT -s uu.uu.uu.uu -p tcp -m tcp --sport 161 -j ACCEPT   # monitoring service
  $ip -A OUTPUT -d uu.uu.uu.uu  -p tcp -m tcp --dport 161 -j ACCEPT  # monitoring service

  $ip -A INPUT -p tcp -m tcp --sport 25 -j ACCEPT
  $ip -A OUTPUT -p tcp -m tcp --dport 25 -j ACCEPT


  # temporary backup if we change from DROP to ACCEPT policies
  $ip -A INPUT -p tcp -m tcp --dport 1:1024 -j DROP
  $ip -A INPUT -p udp -m udp --dport 1:1024 -j DROP


  echo "OK. Check rules with iptables -L -n"

  # end :)

I've been using it for some time , and any kind of modification will be very appreciated if it makes it easier to administrate.

share|improve this answer
add comment

You could lock down the outgoing traffic so that it only responds to connections initiated externally. Does it really need to initiate outbound connections to anywhere on the internet? You'll probably want to whitelist whatever sites you need for software updates.

share|improve this answer
1  
+1 - An exploit script can be stopped in its tracks if it can't "call home" to download its payload. –  Evan Anderson Oct 6 '10 at 13:27
add comment

This looks pretty good, but you could tighten things down a little bit more. The -s flag is the source IP or domain name, and you add "-s 198.23.12.32" or whatever your IP address is to only allow SSH from your source IP. You can also choose a range of source IPs by using CIDR style notation.

You should use caution when logging denied calls. Your server's IP address will get scanned by bots, script kiddies, etc, and the log file could get large rather quickly. Unless you're trying to diagnose a specific problem that you think might be related to someone trying to break your firewall, I would remove this option.

You could also tie in fail2ban to iptables for a pseudo-IDS. fail2ban will scan your log files and can block an IP if they attempt to force their way into your system. For example, if a certain IP address fails to login to SSH 5 times, you can lock them out for an entire day. It also works on FTP and lots of others (including bad bots hitting Apache).I use it on all of my servers to provide some extra cushion from brute-force attacks.

share|improve this answer
    
I actually use DenyHosts since it saves 15MB or so over fail2ban. However, fail2ban is more powerful and works with many applications (not just SSH like DenyHosts). Given that attackers are banned, should I still worry about the log files filling up to fast? Is there a way to rotate files when they get to full? If I disable logging in fail2ban will Denyhosts/Fail2ban still have log entries to scan? Also, the source option would be good for some people - but since I'm aiming for a default ruleset people like myself that move around a lot can't use that option. –  Xeoncross Sep 25 '10 at 18:44
    
@Xeoncross: DenyHosts is a steaming pile imo. I had it running on one machine that was constantly getting intrusion attempts from the Chinese. Over the period of a few months /etc/hosts.deny grew to have a few thousand IPs in it, at which point it caused sshd to exhaust resources on the box, spiking the load up to 60+ on a single CPU machine. I switched to fail2ban and never looked back. –  hobodave Sep 25 '10 at 18:51
    
@hobodave I just started with DenyHosts so I'll keep this mind as the first thing to switch when this becomes a problem. –  Xeoncross Sep 25 '10 at 19:24
1  
@Xeoncross if you want to rotate the iptables log, you can write your own logrotate.d script for it. Take a look at /etc/logrotate.d and copy another one and change the log file name and it will be rotated with other log files. The man page for logrotate explains the various options. –  alanthing Sep 27 '10 at 19:20
add comment

Take a look at Shorewall. The single interface default configuration would be a good starting point. It is easy to configure, and has macros for things like SSH and Web access. It can be configure to lock down the server to the desired level when the firewall is shutdown. With Shorewall-lite, you can run a firewall build on another server. Logging is easy to configure to the desired level.

For a basic HTTP server you want open incoming access to port 80 and port 443 if you use HTTPS. SSH incoming access from a few restricted addresses is usually desired. You may want to lock down outgoing access as well. Open the firewall to required servers and services only. NTP and DNS should be opened, as well as a channel to fetch patches.

share|improve this answer
add comment

I would say this is a pretty good firewall, except that it is geared towards stopping inbound traffic, and not focused on egress or outbound traffic. It is in many cases just as important to focus on connections outbound from a box as those inbound. In the unfortunate case that the machine is actually exploited, it would be nice to be able to prevent downloading additional root kits, or connecting to command and control nodes, or whatever.

BillThor started talking about this above, but I'm just answering with specific examples. One of the nice things about iptables is that it can remember connection state, this can have performance implications on heavily trafficked sites but you could change your inbound access on http/https to only allow reply on established connections for example, or specifically limit certain unprivileged users from having outbound access at all. Then your outbound rules would have RELATED,ESTABLISHED clauses which would prevent a whole host of ancillary attacks and slow down the ones that require a secondary stage to actually exploit a box, which is very common.

Finally, I would say that it is better to set your iptables policy -P DROP rather than have an appended REJECT at the end. It is mostly a matter of preference, but can reduce mistakes when appending to chains with existing rules instead of inserting or flushing/resetting.

share|improve this answer
    
So I should change -A INPUT -j REJECT to -A INPUT -P DROP? –  Xeoncross Mar 10 '12 at 22:45
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.