Site icon David Ramsden

Auditing Cisco ASA Firewall Rules

Today I was auditing a firewall rule set on a Cisco ASA firewall. The firewall has around 399 ACLs (Access Control Lists) comprising of 7272 ACEs (Access Control Entries). Quite a task! Unfortunately I didn’t have any tools to hand such as Cisco Security Manager or something like FirePac to audit the rules and give me some suggestions.

Stage 1 was to visually look at the ACLs and spot the obvious mistakes and remove them. Stage 2 was to then remove any unused names, objects and object-groups. I hacked up a Perl script to do this. The script reads the complete ASA config, gets all the names, objects and object-groups then works out which ones aren’t referenced anywhere else:

#!/usr/bin/perl -w
#

# Dump ASA config to config.txt file.

use strict;

# Read config from config.txt file.
open FP, "config.txt";
 my @config = <FP>;
close FP;
chomp @config;

my $configref = \@config;


# Extract names from config.
my @names = map { /^name \d+\.\d+\.\d+\.\d+ ([A-Za-z0-9-_]+)$/ ? $1 : () } @{ $configref };
# Remove names from config.
@config = grep { $_ !~ /^name / } @config;
# Find unused name references.
foreach my $name (@names) {
        if (!grep { $_ =~ /$name/ } @config) {
                print "name $name unused.\n";
        }
}


# Extract objects from config.
my @objects = map { /^(object|object-group) (network|service) ([A-Za-z0-9-_]+)$/ ? $3 : () } @{ $configref };
# Remove objects from config.
@config = grep { $_ !~ /^(object|object-group) / } @config;
# Find unused object references.
foreach my $object (@objects) {
        if (!grep { $_ =~ /$object/ } @config) {
                print "object $object unused.\n";
        }
}

Stage 3 was to work out which ACLs could be completely removed and which ACLs should be reviewed in more detail. If an ACL with or without ACEs, has a total of 0 hits it can (probably) be removed. If an ACL with ACEs has less than or equal to 100 hits it should be reviewed in more detail because the chances are some of the ACEs associated with it can be removed. A quick and dirty Perl script did the trick:

#!/usr/bin/perl -w
#

# On the ASA:
#  pager 0
#  sh access-list
# Dump text to hitcnts.txt file.

use strict;

# Load ACL hit counts.
open FP, "hitcnts.txt";
        my @hitcnts = <FP>;
close FP;
chomp @hitcnts;

# Iterate through each line in the file.
for my $i (0 .. $#hitcnts) {
        if ($hitcnts[$i] =~ m/^access-list [A-Za-z0-9-_]+ line /) {
                # This is an ACL.

                if (my ($hits) = $hitcnts[$i] =~ m/hitcnt=(\d+)/) {
                        # ACL only has one ACE - get the hit count.

                        if ($hits == 0) {
                                # Zero hits for this ACL.
                                print "Remove ACL ($hits hits): " . $hitcnts[$i] . "\n";
                        } elsif ($hits <= 100) {
                                # Hits <= 100 so should be reviewed.
                                print "Review ACL ($hits total hits): " . $hitcnts[$i] . "\n";
                        }

                        # Move on to the next line.
                        next;
                }

                # ACL probably has more than one ACE so move to the next line, which should be the
                # first ACE of the ACL.
                $i++;

                if ($hitcnts[$i] =~ m/^  access-list /) {
                        # This is an ACE for the ACL.

                        my $total_hits = 0;
                        # Iterate through ACEs for the ACL.
                        for my $x ($i .. $#hitcnts) {
                                if ($hitcnts[$x] =~ m/^access-list /) {
                                        # Hit another ACL so all ACEs have been processed.

                                        last;
                                }

                                # Get hit count for ACE.
                                my ($hits) = $hitcnts[$x] =~ m/hitcnt=(\d+)/;
                                # Total number of hits for the entire ACL.
                                $total_hits += $hits;
                        }

                        if ($total_hits == 0) {
                                # No ACE hits so suggestion is that the ACL can be removed.
                                print "Remove ACL ($total_hits hits): " . $hitcnts[$i-1] . "\n";
                        } elsif ($total_hits <= 100) {
                                # Total ACE hits <= 100 so should be reviewed.
                                print "Review ACEs for ACL ($total_hits total hits): " . $hitcnts[$i-1] . "\n";
                        }
                } else {
                        # Rewind.

                        $i--;
                }
        }
}

I found 181 ACLs that can be immediately removed and a further 16 to be reviewed. With an average of 18 ACEs per ACL, that equates to 3258 ACEs that can removed and 288 that may be able to be removed after a review.

By the end of this journey I should have reduce the rule set by at least 44.80%. After that the rule set just needs re-ordering to optimise the processing.