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.

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

                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.


                                # 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 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.


    1. Hi Yevgenii. Yes, you are right. That was a copy/paste error. Thanks for spotting! I’ve updated it. Let me know how you get on. This has been tested against ASA software release 8.4. I’ve not tried it on anything else yet.

    1. Hi. I’ve not written one. There are commercial tools out there that can help, such as Firepac (now owned by SolarWinds and called SolarWinds Firewall Security Manager). But I’m sure scripting something isn’t too hard. If I get time I’ll look at writing something.

  1. Hi All,

    I new of this, Could you please help how to run this script …

    It will great help us

  2. I am also wondering how we can run this on multiple firewalls? I see no comments in the script on syntax to run this..
    I am sorry for the question.

  3. I just pulled this down and started using it. good script. company don’t want to spend the money for the audit firewall tools so i started looking around to see if anyone has written a script.. so you did.. I can add on more logic as well. thanks for starting the wheel.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

The reCAPTCHA verification period has expired. Please reload the page.