If this is your first visit, be sure to
check out the FAQ by clicking the
link above. You may have to register
before you can post: click the register link above to proceed. To start viewing messages,
select the forum that you want to visit from the selection below.

Threaded View

Iptables NAT Tutorial

Here's the next installment of my iptables tutorials. There are actually 9 scripts that I use to build the firewall, one primary script that calls 8 subscripts. All nine are included in the zip file and the primary script is posted below for your perusal. As always, comments are appreciated. I've been through this thing several times already, but I'm sure as you read it, you'll find errors. It's always hard to proofread your own work. I hope you enjoy it, and I hope you learn something. Perhaps if I missed something, I can learn from you too.

Code:

#############################################################################
# #
# File: build_firewall #
# #
# Written by: str34m3r #
# #
# Purpose: designed for a firewall/router with three legs: an internal LAN, #
# an external LAN, and a demilitarized zone. Network diagram: #
# #
# INT Box 1 -------| EXT Internet |------- DMZ Box 1 #
# | | | #
# INT Box 2 ----| | | | |---- DMZ Box 2 #
# | | | | | #
# INT Box ... ---Switch-----Router/Firewall-----Switch--- DMZ Box ... #
# | | | | #
# INT Box 253 ----| | | |---- DMZ Box 253 #
# | | #
# INT Box 254 -------| |------- DMZ Box 254 #
# #
# Additional files: #
# getIP Used to obtain IP info for an interface #
# getNM Used to obtain netmask info for an interface #
# build_ext_firewall Used to firewall the external interface #
# build_int_firewall Used to firewall the internal interface #
# build_dmz_firewall Used to firewall the DMZ interface #
# build_int_ext_firewall Used between the internal and external LAN's #
# build_int_dmz_firewall Used between the internal and DMZ LAN's #
# build_ext_dmz_firewall Used between the external and DMZ LAN's #
# #
#############################################################################
# For those that are newbies, the term DMZ will be used to refer to the
# demilitarized zone, where you would put any box that offers a service to the
# Since these boxes offer services, they're more likely to be hacked, so we put
# them on their own network. INT is used to refer to your internal LAN where
# the workstations will be. And of course EXT refers to the big bad internet
# where all the blackhats and skiddies live.
#
# I'm not going to teach all the basics of iptables again in this tutorial. For
# a beginners reference to iptables, check my iptables tutorial at:
# http://www.antionline.com/showthread...hreadid=230338
# Since this script uses several other scripts for modularity, you'll need to
# tell it where the others are.
export FW_DIR="/usr/local/firewall"
# In preparation for the rules, we'll load all the modules we'll be needing.
modprobe ip_tables
modprobe iptable_nat
modprobe ip_conntrack_ftp
# Now, from the beginning... As we did before, we'll start by setting the
# default policy for each chain to drop. If you've looked at the previous
# tutorial, you may notice that there are three new chains I'm using this time
# around: PREROUTING, POSTROUTING, and a new OUTPUT. You should also notice the
# -t option. This option controls which table you're working with. There are
# three tables available: filter, nat, and mangle. The filter table is the
# default and is the table used for filtering most traffic. It's default tables
# are INPUT, OUTPUT, and FORWARD. The nat table is only applied to new
# connections and can be used to modify sources and/or destinations. Its
# built-in tables are PREROUTING, POSTROUTING, and OUTPUT. The mangle table
# allows some specialized packet manipulation, but it isn't applicable to our
# firewall, so I'm going to ignore it.
iptables -t filter -P INPUT DROP
iptables -t filter -P OUTPUT DROP
iptables -t filter -P FORWARD DROP
iptables -t nat -P PREROUTING DROP
iptables -t nat -P POSTROUTING DROP
iptables -t nat -P OUTPUT ACCEPT
# The packet traversal in the machine looks something like this:
#
# Entry INPUT ----- Firewall ----- OUTPUT Exit
# | | | |
# | | | |
# | | | |
# PREROUTING --- Decision --- FORWARD ----- Decision --- POSTROUTING
#
# The areas marked decision are where the kernel itself makes about where to
# route the packet. These routing decisions are outside the realm of itables.
# The nat PREROUTING table is where you can alter packets as they come in.
# The nat POSTROUTING table is where you can alter packets before they go out.
# The nat OUTPUT table is actually between the Firewall and the normal OUTPUT
# table, and this is where you could alter packets before routing decisons are
# made, but I've never had much of a use for it. Since I don't use its
# alteration features, and filter OUTPUT is there, I set it to accept and do
# all of my filtering in filter OUTPUT. If any of you use nat OUTPUT, let me
# know how, because I always enjoy learning.
# Next we flush the old rules and clean up the old tables.
iptables -t filter -F
iptables -t filter -X
iptables -t nat -F
iptables -t nat -X
# Before adding rules to our defaults, I'll set up two chains that I use
# extensively throughout the rules. The LOGDROP taget allows me to easily
# log rogue packets before dropping them. By creating a target like this, I
# avoid having to write two rules for each packet I want to log and drop later
# on. And since the LOGDROP targets are in two separate tables, the names don't
# conflict.
iptables -t filter -N LOGDROP
iptables -t filter -A LOGDROP -j LOG --log-level 7
iptables -t filter -A LOGDROP -j DROP
iptables -t nat -N LOGDROP
iptables -t nat -A LOGDROP -j LOG --log-level 7
iptables -t nat -A LOGDROP -j DROP
# I allow everything on the loopback interface to pass through unharmed. If you
# are truly anal, you'll want to filter this too, but even I'm not that anal.
iptables -t nat -A PREROUTING -j ACCEPT -i lo
iptables -t filter -A INPUT -j ACCEPT -i lo
iptables -t filter -A OUTPUT -j ACCEPT -o lo
iptables -t nat -A POSTROUTING -j ACCEPT -o lo
# Since you probably won't (shouldn't) be running Xwindows on your firewall box
# you can probably afford to be a little pickier with your loopback this time,
# so once again, I've included picky settings too. :)
# iptables -t nat -A PREROUTING -j ACCEPT -i lo -s 127.0.0.1 -d 127.0.0.1
# iptables -t filter -A INPUT -j ACCEPT -i lo -s 127.0.0.1 -d 127.0.0.1
# iptables -t filter -A OUTPUT -j ACCEPT -o lo -s 127.0.0.1 -d 127.0.0.1
# iptables -t nat -A POSTROUTING -j ACCEPT -o lo -s 127.0.0.1 -d 127.0.0.1
# Now we need to define our interfaces.
export EXT_IF="eth0"
export INT_IF="eth1"
export DMZ_IF="eth2"
# Since most of the rules in this service are designed for use with entire
# subnets, it's important that we get them right. For each interface, we need
# the IP and the netmask for that interface. I've included two scripts (getIP
# and getNM) to do the work for you so that this script will still work
# with DHCP. 'getIP' and 'getNM' first attempt to gather the necessary
# information from ifconfig because its information should be the most current.
# If, however, the information cannot be obtained from ifconfig (possibly
# because the interface isn't up yet), the scripts parse the interface's
# configuration file to ascertain the IP address and the netmask. Each of these
# scripts looks in '/etc/sysconfig/network-scripts'. If this is not where your
# netowrk config files are, then you can edit the first line in getIP and getNM
# to point to the correct config files.
# Get IP and netmask for the internal interface and build the network variable
export INT_IP=`$FW_DIR/getIP $INT_IF`
if [ ! "INT_IP" ]; then exit 1; fi
export INT_NM=`$FW_DIR/getNM $INT_IF`
if [ ! "INT_NM" ]; then exit 1; fi
export INT_NET="$INT_IP/$INT_NM"
echo "Internal net:$INT_NET"
# Get IP and netmask for the internal interface and build the network variable
export DMZ_IP=`$FW_DIR/getIP $DMZ_IF`
if [ ! "DMZ_IP" ]; then exit 1; fi
export DMZ_NM=`$FW_DIR/getNM $DMZ_IF`
if [ ! "DMZ_NM" ]; then exit 1; fi
export DMZ_NET="$DMZ_IP/$DMZ_NM"
echo "Demilitarized zone:$DMZ_NET"
# Get the IP for the external interface. Since this will be the default route,
# there is no point in worrying about the netmask or network variables.
export EXT_IP=`$FW_DIR/getIP $EXT_IF`
if [ ! "EXT_IP" ]; then exit 1; fi
export EXT_NET="$EXT_IP/0.0.0.0"
echo "External net:$EXT_NET"
# Validate that the packets coming in the internal interface have source IP's
# from the internal IP block. Check the same things on the DMZ packets. If the
# packets aren't what we're expecting, drop them.
iptables -t nat -A PREROUTING -j ACCEPT -i $INT_IF -s $INT_NET
iptables -t nat -A PREROUTING -j LOGDROP -i $INT_IF
iptables -t nat -A PREROUTING -j ACCEPT -i $DMZ_IF -s $DMZ_NET
iptables -t nat -A PREROUTING -j LOGDROP -i $DMZ_IF
# Validate that the packets going out the internal interface have destination
# IP's in the internal IP block. Check the same things on the DMZ packets. If
# the packets aren't what we expect, drop them.
iptables -t nat -A POSTROUTING -j ACCEPT -o $INT_IF -d $INT_NET
iptables -t nat -A POSTROUTING -j LOGDROP -o $INT_IF
iptables -t nat -A POSTROUTING -j ACCEPT -o $DMZ_IF -d $DMZ_NET
iptables -t nat -A POSTROUTING -j LOGDROP -o $DMZ_IF
# At this point, everything we're dealing with should deal with the external
# interface. For external packets, the legal IP range isn't simple. The first
# check we need to make is for local IP's, internal IP's, and DMZ IP's. These
# IP's should never appear on our external interface, so we'll drop any we see.
iptables -t nat -A PREROUTING -j LOGDROP -i $EXT_IF -s 127.0.0.1/8
iptables -t nat -A PREROUTING -j LOGDROP -i $EXT_IF -s $INT_NET
iptables -t nat -A PREROUTING -j LOGDROP -i $EXT_IF -s $DMZ_NET
iptables -t nat -A POSTROUTING -j LOGDROP -o $EXT_IF -d 127.0.0.1/8
iptables -t nat -A POSTROUTING -j LOGDROP -o $EXT_IF -d $INT_NET
iptables -t nat -A POSTROUTING -j LOGDROP -o $EXT_IF -d $DMZ_NET
# We will assume at this point that all incoming packets are from a legal
# external IP and all outgoing packets are to an external IP. This isn't
# technically true due to reserved and unassigned IP blocks, but I rarely see
# any traffic from these IP's, so I'm not going to bother. If you're anal, you
# can check IANA and add the rules to drop these IP blocks yourself. It'll be a
# good learning experience. For now, we're going to move on to the fun stuff:
# Network Address Translation. For the purposes of this tutorial, we'll assume
# that I have a mail server on my DMZ at 192.168.2.2 and a web server at
# 192.168.2.3. Since the world only knows my firewall's external IP address,
# they simply send the packets to the firewall and it reroutes the packets to
# their correct destinations. The mail server has port 25 open to the world and
# also has port 22 open for ssh access. The web server has port 80 open of
# course and is also running an ssh server on port 22. Since we can't redirect
# the firewall's port 22 to both machines, we'll have to do some
# port-translation for at least one of the hosts in addition to doing IP
# translation. And since we're learning, we may as well do port redirection for
# both hosts.
export MAIL_IP="192.168.2.2"
export WEB_IP="192.168.2.3"
# We'll start by redirecting all packets destined for port 25 on the external
# interface to the mail server in the DMZ. We'll also redirect port 2599 to
# port 22 on the mail server. It's important to note that these packets, which
# would have originally passed to the input chain for filtering, will now be
# passed to the forward chain instead.
iptables -t nat -A PREROUTING -j DNAT --to-destination $MAIL_IP \
-p tcp --dport 25 -d $EXT_IP -i $EXT_IF
iptables -t nat -A PREROUTING -j DNAT -p tcp --dport 2599 \
--to-destination $MAIL_IP:22 -d $EXT_IP -i $EXT_IF
# Next we'll redirect all packets destined for port 80 on the external
# interface to the DMZ's web server. We'll also redirect port 2601 to port 22
# on the web server.
iptables -t nat -A PREROUTING -j DNAT --to-destination $WEB_IP \
-p tcp --dport 80 -d $EXT_IP -i $EXT_IF
iptables -t nat -A PREROUTING -j DNAT -p tcp --dport 2601 \
--to-destination $WEB_IP:22 -d $EXT_IP -i $EXT_IF
# NOTE: Using high port numbers will make it a little harder for the script
# kiddies to happen upon your SSH servers, but if anyone is really looking,
# they will find them, so never base your entire defense on an obscure port
# number. Remember: Defense in depth is crucial for network security.
# The last thing we'll do with prerouting is to accept packets destined for
# the firewall.
iptables -t nat -A PREROUTING -j ACCEPT -i $EXT_IF -d $EXT_IP
# We still have one trick up our sleeves for postrouting: masquerading.
# When internal or DMZ boxes connect to boxes out on the internet, we have
# to change the source IP and source port so that our internal IP's don't
# show up on the internet. This process is known as masquerading and is
# handled natively by iptables. So we'll masquerade all packets coming from
# the internal subnet and the DMZ subnet that are going out the external
# interface.
iptables -t nat -A POSTROUTING -j MASQUERADE -o $EXT_IF -s $INT_NET
iptables -t nat -A POSTROUTING -j MASQUERADE -o $EXT_IF -s $DMZ_NET
# The only thing left is postrouting for outbound packets.
iptables -t nat -A POSTROUTING -j ACCEPT -o $EXT_IF -s $EXT_IP
# Build the input and output filters for the internal interface.
$FW_DIR/build_int_firewall
# Build the input and output filters for the internal interface.
$FW_DIR/build_dmz_firewall
# Build the input and output filters for the internal interface.
$FW_DIR/build_ext_firewall
# Build the forward filters between the internal and DMZ interfaces.
$FW_DIR/build_int_dmz_firewall
# Build the forward filters between the internal and external interfaces.
$FW_DIR/build_int_ext_firewall
# Build the forward filters between the external and DMZ interfaces.
$FW_DIR/build_dmz_ext_firewall
# These rules should only trigger when the system doesn't know what to do with
# a packet. I'll send these to LOGDROP since these packets are generally a
# result of something misconfigured on my system and they will help me with
# troubleshooting.
iptables -t nat -A PREROUTING -j LOGDROP
iptables -t filter -A INPUT -j LOGDROP
iptables -t filter -A FORWARD -j LOGDROP
iptables -t filter -A OUTPUT -j LOGDROP
iptables -t nat -A POSTROUTING -j LOGDROP