Friday, 1 April 2011

iptables: white-listing TCP connections to reduce self-0wnage potential

NOTE: This will work in backtrack, ubuntu and pretty much any Linux distro as far as I know

There are times where you would like to open a service to the internet and it is ok to only allow one host/IP address to connect to you, for example:
- Host-to-host transactions
- During a pentest when you open a service so that the victim can connect back to you (you only want the victim to connect to you, not anybody else!)
- It can also happen that one web application is only meant to be used for a limited number of IP addresses, this scenario (allow more than 1 IP) is explained at the end of this post.

When the circumstances allow it, it is a very wise thing to reduce the IP addresses that can connect to your from "anybody from the billions of computers in the internet" to "just this one IP address", this follows the security principle of Least Privilege and drastically reduces the likelihood of being compromised since the only way to attack you if from that single IP address. The reason this works is because TCP IP spoofing is generally not possible in that if someone fakes their IP address they won't be able to complete the TCP three way handsake, which is required to start TCP connections, and hence they won't be able to connect.

Please note that what we are doing here is "allow only this trusted IP address to connect to me", this is known as white-listing because we are only allowing that one IP address and rejecting all others. In practice, this means that we really do not care if an attacker tries to hide their IP address using a proxy because their IP address will still be different from the only one we are allowing.

The only way to break this security control would really be to compromise the host at the trusted IP and if that happens you probably have more to worry about than this :).

The iptables syntax for this is a bit funny so I put together a script for this that I will also share in this post.

Quick iptables 101:
- iptables is a Linux firewall that can be configured via the command line, many people seem to like it :)
- You can see existing iptables rules like this: # iptables -L
- You can remove ("flush") all rules like this (good to start over when you mess up!): # iptables -F

I will start with a blank configuration:

# iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination

Chain FORWARD (policy ACCEPT)
target prot opt source destination

Chain OUTPUT (policy ACCEPT)
target prot opt source destination

For simplicity sake let's assume you want to open port 22 for SSH and restrict it so that only 1 IP is allowed to connect.

You must first create a rule to drop all connections to that port, you can do that like this:
# iptables -A INPUT -i eth0 -p tcp --dport 22 -m state --state NEW,ESTABLISHED -j DROP

You can see the rule you just created by doing "iptables -L" again:
# iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
DROP tcp -- anywhere anywhere tcp dpt:ssh state NEW,ESTABLISHED

The way it is at the moment nobody will be able to connect to port 22.

Now you have to create a rule to allow the trusted IP (192.168.0.1 in this example) to connect:

# iptables -I INPUT -i eth0 -p tcp --dport 22 -s 192.168.0.1 -j ACCEPT

The rule list now looks like this:

# iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT tcp -- 192.168.0.1 anywhere tcp dpt:ssh
DROP tcp -- anywhere anywhere tcp dpt:ssh state NEW,ESTABLISHED

That's it! Now only the host at 192.168.0.1 will be allowed to connect to port 22. You will normally use an internet address instead of a LAN address but both would work.

Because this is a bit cumbesome to do by hand I put together a quick script for it:


# cat restrict_port_to_ip.sh
#!/bin/sh

if [ $# -lt 2 ]; then
echo "Usage: $0 <local port> <only allowed IP> (interface)"
echo "e.g. 192.168.0.1"
exit
fi

local_port=$1
allowed_ip=$2
interface="eth0"
if [ $3 ]; then
interface=$3
fi

# Step 1 - Disallow everything on that port:
iptables -A INPUT -i $interface -p tcp --dport $local_port -m state --state NEW,ESTABLISHED -j DROP

# Step 2 - Allow needed IP on that port:
iptables -I INPUT -i $interface -p tcp --dport $local_port -s $allowed_ip -j ACCEPT


The syntax (which will show up when you try to run this) is pretty self-explanatory but the following illustrates how to use the script:

First let's wipe all rules to start over:
# iptables -F

Now let's verify that the firewall rules are blank:
# iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination

Chain FORWARD (policy ACCEPT)
target prot opt source destination

Chain OUTPUT (policy ACCEPT)
target prot opt source destination

The following creates the two rules so that connections to port 22 are only allowed from 192.168.0.1 on interface eth0:
# restrict_port_to_ip.sh 22 192.168.0.1 eth0

We can now verify that the two rules necessary for this have been created:
# iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT tcp -- 192.168.0.1 anywhere tcp dpt:ssh
DROP tcp -- anywhere anywhere tcp dpt:ssh state NEW,ESTABLISHED

Ok, so this only works for 1 IP address .. what happens if we want to allow more IP addresses?

In that case you just need to run the second command, once per IP address you want to allow. For example:

# iptables -I INPUT -i eth0 -p tcp --dport 22 -s 192.168.0.2 -j ACCEPT
# iptables -I INPUT -i eth0 -p tcp --dport 22 -s 192.168.0.3 -j ACCEPT
# iptables -I INPUT -i eth0 -p tcp --dport 22 -s 192.168.0.4 -j ACCEPT

Now the IP addresses of 192.168.0.2 through 4 are also allowed:

# iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT tcp -- 192.168.0.4 anywhere tcp dpt:ssh
ACCEPT tcp -- 192.168.0.3 anywhere tcp dpt:ssh
ACCEPT tcp -- 192.168.0.2 anywhere tcp dpt:ssh
ACCEPT tcp -- 192.168.0.1 anywhere tcp dpt:ssh
DROP tcp -- anywhere anywhere tcp dpt:ssh state NEW,ESTABLISHED

If you have a list of IP addresses in a file you can even do something like this in a shell one liner:

# for ip in $(cat allowed_ips.txt); do iptables -I INPUT -i eth0 -p tcp --dport 22 -s $ip -j ACCEPT; done