Author Topic: [Perl] IPTables Firewall  (Read 5690 times)

0 Members and 5 Guests are viewing this topic.

Offline iago

  • Leader
  • Administrator
  • Hero Member
  • *****
  • Posts: 17914
  • Fnord.
    • View Profile
    • SkullSecurity
[Perl] IPTables Firewall
« on: July 04, 2006, 01:46:03 am »
I've always wanted to learn how to set up IPTables properly, and this weekend I finally learned.  In my studies, I wrote a script that looks after my new network setup.  Since I was just learning, I commented the heck out of the script, so I figured it might be useful for somebody else. 

Typically, a firewall has three areas:

Internet -- The untrusted, world-wide Internet that we all love
LAN -- The totally trusted network where the PCs live
DMZ -- Demilitarized Zone -- The semi-trusted area where servers live

The idea of setting up a DMZ is to protect the LAN from attacks on the servers.  Here is a brief summary of connections:
LAN -> DMZ -- Allowed
LAN -> Internet -- Allowed
LAN -> Firewall -- Allowed

Internet -> LAN -- Denied
Internet -> DMZ -- Allowed, but restricted
Internet -> Firewall -- Denied

DMZ -> LAN -- Denied
DMZ -> Internet -- Denied (although I allow NTP out to a specific server)
DMZ -> Firewall -- Denied

I eventually intend to make this script a little more flexible with DMZ -> Internet connections, so it'll be possible to install software.  I'd also like to add a blacklist for SSH bruteforcers and stuff. 

Anyways, here is the script I wrote to look after those connections.  It seems to be working quite well:

Quote

#!/usr/bin/perl -w

use strict;

# start|stop|restart|status
my $ACTION = shift;
if($ACTION ne 'start' && $ACTION ne 'stop' && $ACTION ne 'restart' && $ACTION ne 'status')
{
    die("Usage: $0 start|stop|restart|status\n");
}

# The program
my $IPTABLES = '/usr/sbin/iptables';

if($ACTION eq 'status')
{
    print `$IPTABLES -L`;
    exit(0);
}

# Hosts to block
my @BLOCKED;
push(@BLOCKED, "4.2.2.4");

# The mapping of DMZ ports to IPs.  Since there's only one external IP,
# A single port can have a single address, but a single address can have
# multiple ports.
my %DMZ_ALLOWED_INCOMING;
$DMZ_ALLOWED_INCOMING{'TCP/80'}    = '192.168.2.11'; # x86 Website
$DMZ_ALLOWED_INCOMING{'TCP/81'}    = '192.168.2.10'; # x86 Forums

# Allows internal hosts to initiate a connection to an external host.
# Format: protocol/internal ip/external ip:port
# Any of the values can be replaced with '*' for a wildcard
my @DMZ_ALLOWED_OUTGOING;
push(@DMZ_ALLOWED_OUTGOING, "UDP/*/212.85.158.10:123"); # NTP Server

# The internal trusted network
my $LAN_IP = '192.168.1.1';
my $LAN_RANGE = '192.168.1.0/24';
my $LAN_INTERFACE = 'eth1';

# The external, untrusted network
my $INET_IP = '192.168.0.20';
my $INET_RANGE = '192.168.0.0/24';
my $INET_INTERFACE = 'eth0';
   
# The internal, untrusted network
my $DMZ_IP = '192.168.2.1';
my $DMZ_RANGE = '192.168.2.0/24';
my $DMZ_INTERFACE = 'vmnet0';

# The loopback interface
my $LO_IP = '127.0.0.1';
my $LO_RANGE = '255.0.0.0';
my $LO_INTERFACE = 'lo';

my $FROM_LAN = "-i $LAN_INTERFACE -s $LAN_RANGE";
my $FROM_DMZ = "-i $DMZ_INTERFACE -s $DMZ_RANGE";
my $FROM_INET = "-i $INET_INTERFACE";

my $TO_LAN = "-o $LAN_INTERFACE -d $LAN_RANGE";
my $TO_DMZ = "-o $DMZ_INTERFACE -d $DMZ_RANGE";
my $TO_INET = "-o $INET_INTERFACE";


# These three just simplify rules
my $TCP  = '-p TCP';
my $UDP  = '-p UDP';
my $ICMP = '-p ICMP';
my $ALL  = '-p ALL';

my $NEW         = '-m state --state NEW';
my $NEW_EST     = '-m state --state NEW,ESTABLISHED';
my $EST_REL     = '-m state --state ESTABLISHED,RELATED';
my $NEW_EST_REL = '-m state --state NEW,ESTABLISHED,RELATED';

my $SYNACK  = '--tcp-flags SYN,ACK SYN,ACK';
my $ONLYSYN = '--tcp-flags ALL SYN';

my $DROP          = '-j DROP';
my $ACCEPT        = '-j ACCEPT';
my $LOG           = '-j LOG --log-level DEBUG --log-prefix ';
my $CHECK_BLOCKED = '-j blocked';

my $CHECK_BAD_TCP = '-j bad_tcp';

# Ensure that ip forwarding is enabled
`echo '1' > /proc/sys/net/ipv4/ip_forward`;

if($ACTION eq 'stop' || $ACTION eq 'restart')
{
    print "* Stopping IPTables\n";

    print "  -> Setting filter policies to ACCEPT\n";
    print `$IPTABLES -t filter -P INPUT ACCEPT`;
    print `$IPTABLES -t filter -P OUTPUT ACCEPT`;
    print `$IPTABLES -t filter -P FORWARD ACCEPT`;

    print "  -> Setting nat policies to ACCEPT\n";
    print `$IPTABLES -t nat -P PREROUTING ACCEPT`;
    print `$IPTABLES -t nat -P POSTROUTING ACCEPT`;
    print `$IPTABLES -t nat -P OUTPUT ACCEPT`;

    print "  -> Setting mangle policies to ACCEPT\n";
    print `$IPTABLES -t mangle -P INPUT ACCEPT`;
    print `$IPTABLES -t mangle -P FORWARD ACCEPT`;
    print `$IPTABLES -t mangle -P PREROUTING ACCEPT`;
    print `$IPTABLES -t mangle -P POSTROUTING ACCEPT`;
    print `$IPTABLES -t mangle -P OUTPUT ACCEPT`;

    print "  -> Flushing rules\n";
    print `$IPTABLES -t filter -F`;
    print `$IPTABLES -t nat -F`;
    print `$IPTABLES -t mangle -F`;

    print "  -> Deleting custom chains\n";
    print `$IPTABLES -t filter -X`;
    print `$IPTABLES -t nat -X`;
    print `$IPTABLES -t mangle -X`;

    print "* IPTables stopped\n\n";
}

if($ACTION eq 'start' || $ACTION eq 'restart')
{
    print "\n";

    print "* Setting up default policies\n";
    print "  -> INPUT policy -> DROP\n";
    print `$IPTABLES -t filter -P INPUT DROP`;
    print "  -> OUTPUT policy -> DROP\n";
    print `$IPTABLES -t filter -P OUTPUT DROP`;
    print "  -> FILTER policy -> DROP\n";
    print `$IPTABLES -t filter -P FORWARD DROP`;
    print "* Done setting up default policies\n\n";

    print "* Creating chain to find invalid TCP packets\n";
    print `$IPTABLES -t filter -N bad_tcp`;
    print "  -> Dropping all packets that don't have just 'SYN' set\n";
    print `$IPTABLES -t filter -A bad_tcp $TCP ! $ONLYSYN  $NEW $LOG 'New not SYN: '`;
    print `$IPTABLES -t filter -A bad_tcp $TCP ! $ONLYSYN  $NEW $DROP`;
    print "* Bad TCP chain complete\n\n";

    print "* Creating chain to find banned hosts\n";
    print `$IPTABLES -t filter -N blocked`;
    foreach my $blocked(@BLOCKED)
    {
        print "  -> Blocking packets coming from $blocked\n";
        `$IPTABLES -t filter -A blocked -s $blocked $DROP`;
        print "  -> Blocking packets going to $blocked\n";
        `$IPTABLES -t filter -A blocked -d $blocked $DROP`;
    }
    print "* Banned host chain complete\n\n";

    # Filter/INPUT is the filter for incoming packets destined for the router.  We only
    # really want to allow the LAN to get here, and we don't want to be able to initiate
    # a connection with the LAN.
    print "* Beginning filter->INPUT\n";
    print "  -> Checking for invalid TCP packets\n";
    print `$IPTABLES -t filter -A INPUT $TCP $CHECK_BAD_TCP`;
    print "  -> Checking for banned hosts\n";
    print `$IPTABLES -t filter -A INPUT $CHECK_BLOCKED`;
    print "  -> Allowing all loopback packets\n";
    print `$IPTABLES -t filter -A INPUT -i $LO_INTERFACE $ACCEPT`;
    print "  -> Allowing all New and Established LAN Packets\n";
    print `$IPTABLES -t filter -A INPUT -i $LAN_INTERFACE -s $LAN_RANGE $NEW_EST $ACCEPT`;
    print "  -> Logging/dropping everything that doesn't match the rules\n";
    print `$IPTABLES -t filter -A INPUT $LOG 'INPUT packet died: '`;
    print "* filter->INPUT complete\n\n";

    # Filter/OUTPUT is the filter for the outgoing packets leaving the router.  We only
    # really want to allow the router to get to the LAN, and even then we only want to
    # allow it if the LAN initiated the connection.
    print "* Beginning filter->OUTPUT\n";
    print "  -> Checking for invalid TCP packets\n";
    print `$IPTABLES -t filter -A OUTPUT $TCP $CHECK_BAD_TCP`;
    print "  -> Checking for banned hosts\n";
    print `$IPTABLES -t filter -A OUTPUT $CHECK_BLOCKED`;
    print "  -> Allowing all loopback packets\n";
    print `$IPTABLES -t filter -A OUTPUT -o $LO_INTERFACE $ACCEPT`;
    print "  -> Allowing all related and established LAN packets\n";
    print `$IPTABLES -t filter -A OUTPUT -o $LAN_INTERFACE -s $LAN_RANGE $EST_REL $ACCEPT`;
    print "  -> Logging/dropping everything that doesn't match the rules\n";
    print `$IPTABLES -t filter -A OUTPUT $LOG 'OUTPUT packet died: '`;
    print "* filter->OUTPUT complete\n\n";

    # Filter/FORWARD is the filter for packets that are crossing the router.  This is trickier
    # because everything needs to be able to forward to everything under the right conditions.
    print "* Beginning filter->FORWARD\n";
    print "  -> Checking for invalid TCP packets\n";
    print `$IPTABLES -t filter -A FORWARD $TCP $CHECK_BAD_TCP`;
    print "  -> Checking for banned hosts\n";
    print `$IPTABLES -t filter -A FORWARD $CHECK_BLOCKED`;
    print "  -> Allowing the LAN to send connections anywhere\n";
    print `$IPTABLES -t filter -A FORWARD $FROM_LAN $NEW_EST $ACCEPT`;
    print "  -> Allowing the DMZ to make / return connections (bad connections won't be routed)\n";
    print `$IPTABLES -t filter -A FORWARD $FROM_DMZ $EST_REL $ACCEPT`;
    print "  -> Allowing the Internet to return connections to the LAN\n";
    print `$IPTABLES -t filter -A FORWARD $FROM_INET $TO_LAN $EST_REL $ACCEPT`;
    print "  -> Allowing the Internet to initiate and return connections to the DMZ\n";
    print `$IPTABLES -t filter -A FORWARD $FROM_INET $TO_DMZ $NEW_EST_REL $ACCEPT`;

    foreach my $entry(@DMZ_ALLOWED_OUTGOING)
    {
        if($entry =~ m/^([a-zA-Z]+|\*)\/([0-9.]+|\*)\/([0-9.]+|\*):([0-9]+)$/)
        {
            my $protocol = $1;
            my $internal = $2;
            my $external = $3;
            my $port = $4;

            print "  -> Allowing $internal to connect to $external on $protocol/$port\n";

            $protocol = ($protocol eq "*") ? ""              : "-p $protocol";
            $internal = ($internal eq "*") ? "-s $DMZ_RANGE" : "-s $internal";
            $external = ($external eq "*") ? ""              : "-d $external";
            $port     = ($port     eq "*") ? ""              : "--dport $port";

            print `$IPTABLES -t filter -A FORWARD $protocol $internal $external $port $ACCEPT`;
        }
        else
        {
            print "  -> ** INVALID ENTRY: $entry\n";
        }

    }

    print "  -> Logging/dropping everything that doesn't match the rules\n";
    print `$IPTABLES -t filter -A FORWARD $LOG 'FORWARD packet died: '`;

    print "* filter->FORWARD complete\n\n";


    # Nat/PREROUTING allows us to NAT packets before the routing decision has been made.  This
    # is useful when the Internet tries to send a packet to the DMZ.  We change the destination
    # address to the DMZ address before it is routed, then when the routing decision is made the
    # packet ends up in the DMZ and everybody is happy.
    print "* Beginning nat->PREROUTING\n";
    foreach my $protoport(keys(%DMZ_ALLOWED_INCOMING))
    {
        if($protoport =~ m/^([a-zA-Z]+)\/([0-9]*)$/)
        {
            my $protocol = $1;
            my $port = $2;

            my $ip = $DMZ_ALLOWED_INCOMING{$protoport};

            print "  -> NATing external port '$port' on '$protocol' to DMZ ip '$ip'\n";
            `$IPTABLES -t nat -A PREROUTING -p $protocol $FROM_INET --dport $port -j DNAT --to-destination $ip`;
        }
        else
        {
            print "  -> **ERROR**: All NAT ports must be in the form of 'PROTOCOL/port', like 'TCP/80'\n";
            print "  -> Bad rule: $protoport\n";
        }
    }
    print "* nat->PREROUTING complete\n\n";


    # nat/POSTROUTING allows us to NAT packets after routing them.  This is useful when a
    # host behind the NAT is sending out a packet -- its source address is rewritten to point
    # to the router.  LAN packets going to the Internet have to be NATed for the internet to
    # be able to response properly, and LAN packets going to the DNZ ought to be NATed to protect
    # the identity of the internal host
    print "* Beginning nat->POSTROUTING\n";
    print "  -> NATing LAN packets going to the Internet to the address $INET_IP\n";
    print `$IPTABLES -t nat -A POSTROUTING -s $LAN_RANGE $TO_INET -j SNAT --to-source $INET_IP`;
    print "  -> NATing LAN packets going to the DMZ to the address $DMZ_IP\n";
    print `$IPTABLES -t nat -A POSTROUTING -s $LAN_RANGE $TO_DMZ  -j SNAT --to-source $DMZ_IP`;

    foreach my $entry(@DMZ_ALLOWED_OUTGOING)
    {
        if($entry =~ m/^([a-zA-Z]+|\*)\/([0-9.]+|\*)\/([0-9.]+|\*):([0-9]+)$/)
        {
            my $protocol = $1;
            my $internal = $2;
            my $external = $3;
            my $port = $4;

            print "  -> NATing outgoing packets from $internal to $external on $port/$protocol\n";

            $protocol = ($protocol eq "*") ? ""              : "-p $protocol";
            $internal = ($internal eq "*") ? "-s $DMZ_RANGE" : "-s $internal";
            $external = ($external eq "*") ? ""              : "-d $external";
            $port     = ($port     eq "*") ? ""              : "--dport $port";

            print `$IPTABLES -t nat -A POSTROUTING $protocol $internal $external $port -j SNAT --to-source $INET_IP`;
        }
        else
        {
            print "  -> ** INVALID ENTRY: $entry\n";
        }
    }

    print "* nat->POSTROUTING complete\n\n";

    print "IPTables startup complete!\n\n";
}

<edit> Remove stupid smilies
« Last Edit: July 07, 2006, 08:17:22 am by iago »

Offline Sidoh

  • Moderator
  • Hero Member
  • *****
  • Posts: 17634
  • MHNATY ~~~~~
    • View Profile
    • sidoh
Re: [Perl] IPTables Firewall
« Reply #1 on: July 04, 2006, 06:28:23 am »
Very cool!  Maybe I'll try this out for grins. :)

Offline iago

  • Leader
  • Administrator
  • Hero Member
  • *****
  • Posts: 17914
  • Fnord.
    • View Profile
    • SkullSecurity
Re: [Perl] IPTables Firewall
« Reply #2 on: July 04, 2006, 08:49:24 pm »
I updated it (modified my post).

Changes:
- IPs can be blocked by adding elements to the BLOCKED array
- More granular control of DMZ's outbound connections

Offline GameSnake

  • News hound
  • Hero Member
  • *****
  • Posts: 2937
    • View Profile
Re: [Perl] IPTables Firewall
« Reply #3 on: July 07, 2006, 04:28:03 am »
Perl looks somewhat like Python, a language I know well. Maybe I should try to learn Perl...

Offline iago

  • Leader
  • Administrator
  • Hero Member
  • *****
  • Posts: 17914
  • Fnord.
    • View Profile
    • SkullSecurity
Re: [Perl] IPTables Firewall
« Reply #4 on: July 07, 2006, 08:16:59 am »
If somebody asked me which language is the most useful, I'd say Perl.  I've used it for all kinds of text-processing chores that would have taken me hours of find/replace otherwise.  I especially like these constructs:

Quote
foreach my $entry(@DMZ_ALLOWED_OUTGOING)
    {
        if($entry =~ m/^([a-zA-Z]+|\*)\/([0-9.]+|\*)\/([0-9.]+|\*):([0-9]+)$/)
        {
            my $protocol = $1;
            my $internal = $2;
            my $external = $3;
            my $port = $4;

            print "  -> Allowing $internal to connect to $external on $protocol/$port\n";
............

Which let you split a string apart very easily.  I know they look pretty ugly, but it's really quite slick.

Offline rabbit

  • x86
  • Hero Member
  • *****
  • Posts: 8092
  • I speak for the entire clan (except Joe)
    • View Profile
Re: [Perl] IPTables Firewall
« Reply #5 on: July 07, 2006, 08:26:12 am »
regex FTW!

Offline GameSnake

  • News hound
  • Hero Member
  • *****
  • Posts: 2937
    • View Profile
Re: [Perl] IPTables Firewall
« Reply #6 on: July 07, 2006, 07:54:45 pm »
What advanteges does Perl have over Java, for example?

Offline rabbit

  • x86
  • Hero Member
  • *****
  • Posts: 8092
  • I speak for the entire clan (except Joe)
    • View Profile
Re: [Perl] IPTables Firewall
« Reply #7 on: July 07, 2006, 09:11:19 pm »
regex, simplicity, no need for a VM or a compiler, fast, much less OOP.

Offline iago

  • Leader
  • Administrator
  • Hero Member
  • *****
  • Posts: 17914
  • Fnord.
    • View Profile
    • SkullSecurity
Re: [Perl] IPTables Firewall
« Reply #8 on: July 07, 2006, 09:49:40 pm »
It's just a different language.  There are some things I write in Java, some things I write in Perl, some things I write in C, Bash, PHP, etc.  No one language does everything I want, but between all the languages I can do nearly everything. 

Perl is great for quick and dirty programs.  The code isn't typically (although it CAN be) structured.  It is very easy to process a string, and can do that very fast.  It can also store and access arrays and hashtable (associative arrays) very quickly and easily for the programmer.

The problem with Perl is that, because of its quick and dirty design, it doesn't scale up to large applications too well. 

Offline GameSnake

  • News hound
  • Hero Member
  • *****
  • Posts: 2937
    • View Profile
Re: [Perl] IPTables Firewall
« Reply #9 on: July 08, 2006, 03:13:13 am »
regex, simplicity, no need for a VM or a compiler, fast, much less OOP.
Perl seems harder than Java to me.

Offline Sidoh

  • Moderator
  • Hero Member
  • *****
  • Posts: 17634
  • MHNATY ~~~~~
    • View Profile
    • sidoh
Re: [Perl] IPTables Firewall
« Reply #10 on: July 08, 2006, 03:21:56 am »
Perl seems harder than Java to me.

Hahahahahahahahhahaha hahah haha hahahahahahaha hahaahahah ahahahah.

Offline iago

  • Leader
  • Administrator
  • Hero Member
  • *****
  • Posts: 17914
  • Fnord.
    • View Profile
    • SkullSecurity
Re: [Perl] IPTables Firewall
« Reply #11 on: July 08, 2006, 10:17:53 am »
regex, simplicity, no need for a VM or a compiler, fast, much less OOP.
Perl seems harder than Java to me.
Nah, the only tricky part of Perl is the regex's.  Everything else is fairly straight forward. 

Offline rabbit

  • x86
  • Hero Member
  • *****
  • Posts: 8092
  • I speak for the entire clan (except Joe)
    • View Profile
Re: [Perl] IPTables Firewall
« Reply #12 on: July 08, 2006, 12:46:53 pm »
Well, as long as you keep projects short.  Big programs should not be written in perl, unless of course you like lots of foggyness.

Offline iago

  • Leader
  • Administrator
  • Hero Member
  • *****
  • Posts: 17914
  • Fnord.
    • View Profile
    • SkullSecurity
Re: [Perl] IPTables Firewall
« Reply #13 on: July 08, 2006, 01:32:43 pm »
Big projects CAN be written in Perl, and they can even be written WELL in Perl, but it's not typically done, and that's not how  Perl is designed.