Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

11 Steps to Secure Your Servers Part 9: Firewalling

DZone's Guide to

11 Steps to Secure Your Servers Part 9: Firewalling

Part 9 of this series of posts on server security demonstrates firewall setup and configuration.

· Performance Zone
Free Resource

This is part 9 of a series of posts on server security from Inversoft's 2016 Guide to User Data Security.

This section covers how to prevent access to the servers except on specific ports and from specific IP addresses.

Configuration is different for each server to ensure that each server prevents as much direct access as possible. For the Application Server, you will lock down all ports except those needed to access your application's server-side components and to access the server via SSH. For the Database Server, you will lock down all ports except the database port and SSH port. In addition, on the Database Server you will also prevent access from everywhere except the Application Server. If you are using Amazon, they provide a web-based firewall configuration for servers and you will use that rather than IPTables to manage access to your servers.

First, install the firewall package. This package will ensure that the firewall rules are loaded each time the server is restarted. To install this package, execute this command in the root terminal:

$ apt-get install iptables-persistent

Make sure you save both the rules.v4 and rules.v6 file during this installation process.

Now, setup the firewall on the Application Server. Your application isn't running yet, but when it does run it will be listening on two ports. These will vary based on your setup, but our examples will use 3003 for HTTPS web requests and port 3000 for HTTP web requests. We will actually forward requests from the standard ports 80 for HTTP and 443 for HTTPS to ports 3000 and 3003 respectively. This forwarding is covered later.

Likewise, the Application Server will listen on port 22 for SSH requests. Since you installed the persistent IPTables package, you can simply edit the rules file to set your rules. Copy and paste the following content into the /etc/iptables/rules.v4 file (via nano or any other editor):

        *filter
        # Allow all outgoing, but drop incoming and forwarding packets by default
        :INPUT DROP [0:0]
        :FORWARD DROP [0:0]
        :OUTPUT ACCEPT [0:0]

        # Custom per-protocol chains
        :UDP - [0:0]
        :TCP - [0:0]
        :ICMP - [0:0]

        # Acceptable UDP traffic

        # Acceptable TCP traffic
        -A TCP -p tcp --dport 22 -j ACCEPT
        -A TCP -p tcp --dport 443 -j ACCEPT
        -A TCP -p tcp --dport 80 -j ACCEPT
        -A TCP -p tcp --dport 3000 -j ACCEPT
        -A TCP -p tcp --dport 3003 -j ACCEPT

        # Acceptable ICMP traffic

        # Boilerplate acceptance policy
        -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
        -A INPUT -i lo -j ACCEPT

        # Drop invalid packets
        -A INPUT -m conntrack --ctstate INVALID -j DROP

        # Pass traffic to protocol-specific chains
        ## Only allow new connections (established and related should already be handled)
        ## For TCP, additionally only allow new SYN packets since that is the only valid
        ## method for establishing a new TCP connection
        -A INPUT -p udp -m conntrack --ctstate NEW -j UDP
        -A INPUT -p tcp --syn -m conntrack --ctstate NEW -j TCP
        -A INPUT -p icmp -m conntrack --ctstate NEW -j ICMP

        # Commit the changes
        COMMIT

        *raw
        :PREROUTING ACCEPT [0:0]
        :OUTPUT ACCEPT [0:0]
        COMMIT

        *nat
        :PREROUTING ACCEPT [0:0]
        :INPUT ACCEPT [0:0]
        :OUTPUT ACCEPT [0:0]
        :POSTROUTING ACCEPT [0:0]

        -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 3000
        -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 3003

        # Commit the changes
        COMMIT

        *security
        :INPUT ACCEPT [0:0]
        :FORWARD ACCEPT [0:0]
        :OUTPUT ACCEPT [0:0]
        COMMIT

        *mangle
        :PREROUTING ACCEPT [0:0]
        :INPUT ACCEPT [0:0]
        :FORWARD ACCEPT [0:0]
        :OUTPUT ACCEPT [0:0]
        :POSTROUTING ACCEPT [0:0]
        COMMIT

A complete explanation of the IPTables rule file format is out of scope for this guide; however, we will cover some of the basics so that you can configure your file to suit your needs. IPTables rule files are broken down into Tables. Tables are defined using an asterisk character followed by the table name. Everything after the table definition is included in that table's rules until the COMMIT command is hit.

The filter table is the most important table above, but the nat table is also used and we'll cover that shortly. The filter table defined like this:

        *filter
        ...
        COMMIT

We'll ignore the raw, security and mangle tables for now. You can find more information about them online if you are interested.

A table can hold one or more chains. There are a number of predefined chains in each table, but for the filter table the three chains are INPUTOUTPUT and FORWARD. In the nat table, we will use the PREROUTING chain as well. Here are the definitions of these chains:

  • INPUT - this chain is used when packets are sent from an external source to the server
  • OUTPUT - this chain is used when packets are sent from the server to an external source
  • FORWARD - this chain is used when the server is forwarding packets between two external computers (rarely used)
  • PREROUTING - this chain is used to modify incoming packets before they are delivered

Chains are defined using a colon character followed by the name of the chain. Here's an example of defining a new chain called FOOBAR:

        :FOOBAR [0:0]

NOTE: The numbers inside the brackets are the number of bytes sent and received on the chain. They are mostly for informational purposes and in the example above, we initialize everything to zero.

It is also important to keep in mind that a connection based protocol like TCP will use both the INPUT and the OUTPUT chain because packets flow in both directions.

When network packets arrive at the server, they are handled by one of these three chains. You can define all of your rules directly on these chains or you can define new chains and JUMP to them from the three predefined chains. The rules file above defines three new chains, one for each of the protocols you are probably interested in handling on your servers. The rules file also defines how the predefined chains JUMP to the new chains. The chains are defined at the top of the file and are named TCPUDP and ICMP to map to each of the protocols.

The JUMP rules are midway through the file and look like this:

        -A INPUT -p udp -m conntrack --ctstate NEW -j UDP

Here's how this rule reads: "Append to the INPUT chain (-A INPUT), for protocol UDP (-p udp), when new connections are made (-m conntrack -cstate NEW), jump to the UDP chain (-j UDP). As you can see, there is a similar rule for each of the new chains we created for each protocol.

Each rule must define a policy that determines how packets are handled. This is done by indicating one of these 3 policies:

  • ACCEPT - the packet is allowed and whatever service is listening for network traffic on the specific port will be sent the packets
  • DROP - the packet is completely ignored; the external computer that sent the packet will not receive a response and will be forced to time-out the connection
  • REJECT - a response is sent to the external computer indicating that the server has rejected the packet

In the configuration above, we initialize the INPUT and FORWARD chains to DROP everything by default. This prevents your server from accepting packets unless you explicitly define a rule. The OUTPUT chain on the other hand is set to ACCEPT by default. In most cases, if you are on the server, you'll likely want to access other servers. You might need to do a DNS lookup or download packages to install, but it is generally safe to leave the OUTPUT chain open.

The file above sets up a few default rules for the predefined INPUT chain. These rules are:

        # Boilerplate acceptance policy
        -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
        -A INPUT -i lo -j ACCEPT

        # Drop invalid packets
        -A INPUT -m conntrack --ctstate INVALID -j DROP

Here's a quick explanation of these rules:

  • All traffic on the loopback interface (packets going from the server to itself) are allowed.
  • All traffic on connections that have already been established or related connections are allowed.
  • All invalid packets are dropped. Switches and other networking systems can mark packets as invalid, so ignoring those packets won't cause any issues.

The final rules are setup for each of the protocols you are interested in. By default, UDP and ICMP traffic are completely ignored. The file above assumes that you will only be using TCP connections to the server, so it only defines rules for that filter chain. If you need to use UDP or ICMP, you can add additional rules for those protocols. Here are the rules for the TCP chain:

        -A TCP -p tcp --dport 22 -j ACCEPT
        -A TCP -p tcp --dport 443 -j ACCEPT
        -A TCP -p tcp --dport 80 -j ACCEPT
        -A TCP -p tcp --dport 3000 -j ACCEPT
        -A TCP -p tcp --dport 3003 -j ACCEPT

These rules read like this, "for TCP packets (-A TCP -p tcp), on port 22 (--dport 22), accept them (-j ACCEPT)". There is a separate rule for each port that should be open on the server. You'll notice that the server listens on the standard HTTP and HTTPS ports, but also port 3000 and 3003, which are the ports our example application will be listening on.

In addition to the filter table, the configuration above also makes use of the nat table. The nat table is used because later in this guide, you will run your application as a non-privileged user (i.e. not the root user). This is to ensure that breaches to the application won't allow hackers full access to the server. Linux servers don't allow non-privileged users to bind ports below 1000. Therefore, the IPTables configuration forwards traffic on port 80 to port 3000 and traffic on port 443 to port 3003. To accomplish this port forwarding, we make use of the PREROUTING chain in the nat table.

Under the nat table, there are two rules defined:

        -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 3000
        -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 3003

These rules read like this, "append to the PREROUTING chain (-A PREROUTING) for TCP packets (-p tcp), forward destination port 80 (--dport 80 -j REDIRECT) to port 3000 (--to-port 3000).

Now that the rules are setup, load this configuration by executing this command:

$ service netfilter-persistent reload

Next, configure the firewall on the Database Server. Each database will have different ports that it needs open. You will only allow connections from your Application Server's private IP address, which you will need to know and insert into the rules below. Here are the IPTable rules for the Database Server.

        *filter
        # Allow all outgoing, but drop incoming and forwarding packets by default
        :INPUT DROP [0:0]
        :FORWARD DROP [0:0]
        :OUTPUT ACCEPT [0:0]

        # Custom per-protocol chains
        :UDP - [0:0]
        :TCP - [0:0]
        :ICMP - [0:0]

        # Acceptable UDP traffic

        # Acceptable TCP traffic
        -A TCP -p tcp --dport 22 -s 192.168.197.56 -j ACCEPT
        -A TCP -p tcp --dport 3306 -s 192.168.197.56 -j ACCEPT

        # Acceptable ICMP traffic

        # Boilerplate acceptance policy
        -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
        -A INPUT -i lo -j ACCEPT

        # Drop invalid packets
        -A INPUT -m conntrack --ctstate INVALID -j DROP

        # Pass traffic to protocol-specific chains
        ## Only allow new connections (established and related should already be handled)
        ## For TCP, additionally only allow new SYN packets since that is the only valid
        ## method for establishing a new TCP connection
        -A INPUT -p udp -m conntrack --ctstate NEW -j UDP
        -A INPUT -p tcp --syn -m conntrack --ctstate NEW -j TCP
        -A INPUT -p icmp -m conntrack --ctstate NEW -j ICMP

        # Commit the changes
        COMMIT

        *raw
        :PREROUTING ACCEPT [0:0]
        :OUTPUT ACCEPT [0:0]
        COMMIT

        *nat
        :PREROUTING ACCEPT [0:0]
        :INPUT ACCEPT [0:0]
        :OUTPUT ACCEPT [0:0]
        :POSTROUTING ACCEPT [0:0]
        COMMIT

        *security
        :INPUT ACCEPT [0:0]
        :FORWARD ACCEPT [0:0]
        :OUTPUT ACCEPT [0:0]
        COMMIT

        *mangle
        :PREROUTING ACCEPT [0:0]
        :INPUT ACCEPT [0:0]
        :FORWARD ACCEPT [0:0]
        :OUTPUT ACCEPT [0:0]
        :POSTROUTING ACCEPT [0:0]
        COMMIT

The primary difference between the Application Server's and the Database Server's IPTables is that the Database Server only allows connections on port 22 for SSH and 3306 for MySQL. Plus, only connections that originate from the Application Server to the Database Server are allowed. The lines that setup this configuration are:

        -A TCP -p tcp --dport 22 -s 192.81.133.144 -j ACCEPT
        -A TCP -p tcp --dport 3306 -s 192.81.133.144 -j ACCEPT

You can also define multiple IP addresses using a comma separated list or subnet masks (e.g. 192.168.1.2,192.168.1.3 or 192.168.1.0/24).

It might seem like a pain to have to log into the Application Server in order to log into the Database Server. Nevertheless, this is usually a good idea. By doing so, the Database Server (where all your user data is stored) is protected from brute force attacks. If you put yourself in a hacker's shoes this security measure makes sense:

You have finally managed to log into the Application Server because the Database Server wasn't publicly accessible. However, you still have the daunting task of hacking either the MySQL password on the current server by gaining root access or hacking the login to the Database Server.

Both are challenging feats and it is likely the hacker will be discovered well before they accomplish either task.

Find our Github project here: https://github.com/inversoft/2016-security-scripts. This project contains a set of scripts you can execute from your local computer to secure a remote server. 

Topics:
firewall ,firewall rules ,secure code ,security best practices ,security ,server

Published at DZone with permission of Kelly Strain. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}