The first line of defense in a network is the access control list (ACL) on the edge firewall. Some vendors call these firewall rules, rule sets, or something similar. To keep the discussion focused, this post will look only at the Cisco ASA firewall, but many of the ideas are applicable to just about every device on the market.

Cisco uses ACLs for many other purposes besides controlling access. ACLs can define which routes will be distributed over a routing protocol. They can control quality of service (QoS) rules and other policies as well. But for this article I just want to talk about the ACLs that filter traffic flowing into, through, and out of the firewall. Just about every firewall implementation will need this kind of ACL.

The challenge is that while these ACLs can be fairly simple in concept, they quickly become large and unwieldy if they aren’t carefully organized and managed. Note: many of these rules can also be used to customize cloud-based firewalls, or firewalls as a service.

5 rules for building ACLs

I use several general rules when building my network firewall’s security policy and applying ACLs to interfaces on Cisco ASA firewalls. Note that this is simply how I do it. They aren’t hard rules, but they are based on many years of experience and a lot of mistakes and places where I painted myself into a difficult corner.

My goal is always to make the intent of the configuration as clear as possible, and to make it easy to maintain and update the firewall over time.

I always construct my rules using the command-line interface (CLI). I don’t like using the Cisco ASDM web interface to configure ASA firewalls because I don’t find it makes anything easier to understand or faster to deploy.

When I first started using ASA firewalls, I did use ASDM. But most of us who work with these systems regularly find the CLI easier in the long run.

One of the biggest differences is that the ASDM interface automatically creates a lot of object groups that wind up having arbitrary and meaningless names. This alone I consider so unhelpful that I always encourage administrators to build their ACLs from the CLI.

And with that, let’s take a look at five simple rules setting up ACLs in your Cisco ASA Firewall.

1. Always apply ACLs inbound on all interfaces

Every interface should have an ACL, even if it’s a trivial single line. I don’t like to apply ACLs outbound on the interfaces because I want to use the firewall’s internal compute and memory resources as efficiently as possible. I don’t want to receive a packet, perform address translation and application inspection, scan it against IDS rules, and then put it in the outbound buffer, only to drop it. That’s rather inefficient. Since firewalls often create performance bottlenecks in networks, I would prefer to apply those resources more carefully.

This is true for the modern Cisco ASA devices that are the focus of this particular article, but it’s also true for essentially every next-generation firewall on the market. If we know we’ll never accept a packet from North Korea, let’s just drop it and not bother to inspect it any further.

There are exceptions, of course. Cisco wouldn’t have included the ability to apply outbound ACLs if they weren’t needed. But I would consider instances where outbound rules are needed as corner cases that can often be avoided if you design your rule sets more carefully.

2. Name the ACL after the interface on which the rule will be applied

I like to name my main interfaces inside and outside. If there are additional interfaces, such as DMZs, I try to give them clear and simple names like, well, like DMZ.

The ACL applied to the inbound path on my inside interface will be inside_in. The one on the outside interface will be outside_in. If I had an outbound ACL, it might be called outside_out.

Note that “in” and “out” are from the interface’s point of view. The packets received inbound to the device through that interface are “in”. The packets sent outbound from that interface are “out”.

Normally, I modify these ACLs incrementally, simply adding or deleting individual lines one at a time. However, if I need to do a wholesale change, I generally keep the old ACL and create a new one, but add a simple revision number to the name, such as inside_in_2. This way, it’s very easy to roll back the change to the old version if there’s a problem.

3. Use remarks in your ACLs to internally document your intentions

The more you can make the configuration of your firewall self-documenting, the easier it will be to manage it going forward. (I’ll show some specific examples of remark lines a little later).

4. Use object groups

An object-group is a convenient way of organizing things like IP addresses or protocols. Using object-groups allows you to create an access rule for one group of hosts to access another group of hosts over a common set of protocols with a single command, as long as you’ve already defined those groupings. The other big advantage of object-groups is that you can re-use them.

For example, I might want to block a particular set of malicious IP addresses from ever accessing my network from the outside. If I use the same object-group on the inside interface, I can also prevent anybody inside my network from ever accessing these same malicious external hosts. And if I add a new host to that object-group, I automatically update both those inbound and outbound rules.

Similarly, I could have an object-group for my web servers. Or I could have an object-group that includes the common HTTP and HTTPS protocols so I can apply them simultaneously, all in one line.

The other important thing to mention with object-groups is that they should have meaningful names. If you have an object-group for standard web protocols like HTTP and HTTPS, give it a name like WebProtocols, making it obvious what it is and how you plan to use it.

5. Make your ACL as specific as possible.

Don’t permit “any” hosts if you can narrow it down. Make those “permit” rules as specific as possible. The same goes for protocols. Don’t permit all IP protocols if you really mean a particular protocol. Firewalls are security devices, so don’t undermine your security.

ACL types

ACLs come in four main types used in ASAs: Standard, Extended, EtherType, and Webtype. Each ACL type has a different application, depending on where it’s deployed.

Standard. A standard ACL is designed to protect a network using only the destination address. These are typically used in simple deployments, and are used by only a few protocols like VPN filters and route maps (though route maps can also use extended ACLs, so it’s rarely used in this case either). Standard ACLs do not provide robust security.

Extended. Building on a standard ACL, using extended ACLs means you can also allow or block source addresses in addition to destination. Extended ACLs can also be applied to traffic based on a variety of protocols: IP, ICMP, TCP, and UDP, as well as service policies, AAA rules, WCCP, Botnet Traffic Filter, and VPN group and DAP policies. Among the most common ACLs you will encounter.

EtherType. This type of filter typically applies to Layer 2 (the data link layer) packet traffic not associated with IP data. This ACL is only for use on bridge group interfaces.

Webtype. Employed to filter “clientless” SSL VPN sessions, Webtype ACLs can be used to deny traffic from URLs and other destinations.

Note: You can find a description of ACL types and common uses on the Cisco documentation page here.

Overall ACL structure

ACLs are executed sequentially for each new session. If there’s a match, either permitting or denying the packet, the firewall stops checking. So order matters. I’ve seen many cases where certain lines in an ACL never have an effect because an earlier rule overrides them.

I like to build my ACLs in a structured way. First, I include a relatively small and very specific whitelist. It includes things that I know are always allowed, and overrides any blacklist rules that might come later.

For example, I might want to always permit a VPN tunnel from the static IP address of a remote office. At one client site, I created a rule that would be updated regularly that included the IP address of the hotel where a senior executive was staying that week. I removed it when they returned to the office.

Most of the time, I also include a general blacklist, then all the specific rules.

Start each section with a remark explaining what it is. Each individual rule should also have a separate remark. If the client has a good change control system, I like to also include the ticket number and initials of the engineer making the change in each of the individual rule remark lines.

The following configuration fragment shows a very simple example, with only a single rule in each section:

access-list outside_in remark Section 1 - Specific whitelist 
access-list outside_in remark Temporary exception - #50662 - 2020-10-20 - KD
access-list outside_in extended permit tcp object-group SPECIAL_DEVICES any eq http
access-list outside_in remark Section 2 - General blacklist 
access-list outside_in remark Suspicious Ranges - #11246 - 2015-11-05 - KD
access-list outside_in extended deny ip object-group SuspiciousRanges any
access-list outside_in remark Section 3 - General whitelist 
access-list outside_in remark web servers - #24548 - 2016-08-19 - KD
access-list outside_in extended permit tcp object-group any WebServers object-group WebProtocols
access-list outside_in remark Section 4 - Specific rules
access-list outside_in remark mail relay - #10456 - 2015-07-29 - KD
access-list outside_in extended permit tcp object-group MailRelay object-group MailServer object-group MailProtocols
access-group outside_in in interface outside

That last “access-group” command is what we use to apply this particular ACL to the interface. In this case, it’s applied inbound to the interface named outside.

The blacklist

Now let’s look at the sections in more detail. My general blacklist is usually a list of sites or IP address ranges representing geographic regions that I will never accept anything from. For example, if the organization never expects legitimate traffic from a residential broadband ISP in suburban Moscow or sub-Saharan Africa, block it. And if you’re being attacked from a web hosting service in Romania, block it. I often do this all in a single object-group that I call “SuspiciousRanges”.

Note that this is also why I put my specific whitelist and temporary exceptions above the general blacklist. If there’s a legitimate customer in Lagos, Nigeria, or an executive who likes to holiday in Moscow, I can put these specific addresses into my whitelist without weakening my blacklist.

One of my daily activities is to monitor the firewall logs for people attacking my network, and to add their IPs to this object group. I always look up the IPs against DNS and see what they are. If the source is an ISP in a country where I never expect to see legitimate traffic, then I might block the whole allocated range for that ISP. Otherwise, I might just block the single host that’s attacking me.

object-group network SuspiciousRanges
 description Hosts and networks to be blocked
 network-object host

The above example object-group has only two useful lines. This particular object-group will generally grow over time to be extremely large.

I like to keep a spreadsheet just for this rule, explaining why I’ve included each line. If it was an attack, then I include the date of the attack and the specific IP addresses that originated the attack. This way, if there’s ever a complaint from a user saying that a legitimate host has been blocked, I can look back and see whether that particular host is collateral from an overly general rule. This can happen in particular with large web hosting services, where IP addresses might be re-used, or where there are many customers and only some of them are malicious.

Counters and statistics

One of the most useful but neglected features of Cisco ASA ACLs is the statistical data provided by the “show access-list” command. This command conveniently provides a counter of the number of times each rule was matched.

And, in the case of object-group lines, which could include hundreds or thousands of individual entries, it breaks out the hit counts for each individual entry. These counts give you an instant way of telling whether a particular rule is being used.

Such information can be helpful when trying looking at particulars in network performance troubleshooting: why a traffic pattern is either passing or being blocked incorrectly. Suppose, for example, that you’re trying to allow a particular external network to access some sensitive DMZ server, but it isn’t working. To accommodate the access, you probably added a narrowly defined “permit” command to your outside_in ACL. Use the “show access-list outside_in” command and find the line you created for this purpose. If it has a hit count of zero, then you know some other command higher up in the ACL is blocking your special access.

The other thing I often use the counters in the “show access-list” output for is to see whether specific lines are being used at all. If they’re never used, they might be unnecessary, making them candidates for removal next time I’m cleaning up my firewall configuration.

It’s also useful to remember that every ACL ends with an implicit “deny all” rule. This means that anything not explicitly allowed will be rejected. However, it’s often convenient to make this explicit and end the ACL with a “deny ip any any” rule.

The advantage to doing so is that the “show access-list” command will give you statistics on how often packets are being rejected by falling off the end of the ACL. If I’m doing formal statistical analysis, then I can add up all the individual counters and get reliable percentages of how many packets are being rejected or accepted by each individual command.

A few additional thoughts

One of the more interesting features of these ACLs is the ability to use Fully Qualified Domain Names (FQDN). It’s a relatively new feature, which I presume Cisco added because every other firewall vendor had such a feature. However, I have to say I don’t really like to use DNS-based rules in my firewall ACLs.

Here’s my worry. Suppose somebody knew that I trusted, say, by name in my firewall. They could launch an attack in which they hit my firewall with bogus DNS packets and trick it into accepting packets from some other network.

There’s a workaround to this problem, though. If you use FQDN-based ACL entries, you can (and should) enable the “dns-guard” feature on your firewall. It’s an inspection rule that validates DNS responses.

Another thing to consider when building ACLs is that they’re static and based purely on Layer 3 and 4 features like IP addresses and port numbers. This doesn’t really help you if you’re concerned about more sophisticated attacks.

For example, if I use an ACL to allow HTTP access to my web server from the internet, the firewall passes all of those HTTP packets. In fact, it will pass anything that has the right TCP port information, regardless of whether it’s actually HTTP. And it will completely miss more sophisticated attacks like SQL injections, cross-site scripting, and so forth. So I like to think of my firewall ACL as simply a first order traffic filter to give the intrusion detection/prevention system (IDS/IPS) a slightly easier job.

Cisco’s current generation of ASA 5500x firewalls include the option of running SourceFire IDS/IPS software on a virtual machine inside the firewall, and their integrated FirePower firewalls do it natively. Or you could use some other vendor’s IDS/IPS. The important thing is to remember that you aren’t secure just because you’ve built a rock solid ACL.

Get templates for network assessment reports, presentations, pricing & more—designed just for MSPs.

Ebook cover - The Ultimate Guide to Selling Managed Network Services
  1. Richard Avatar

    Thanks for the article it’s helped me a lot. The issue I’m having is actually blocking the IP range. Basically I’m getting hit daily by someone spoofing IP Addresses from an ISP. Every day it’s a different Address from this network. Nobody accessing my network would ever be coming in from this ISP so I want to block it. I’ve tried blocking the the network and /24 after the network IP. I’ve also tried using subnet but they’re still hitting us. It looks like the only reason they’re not getting in is because they don’t know an account or password.
    Any help on blocking IP range instead of an address?

    1. Kevin Dooley Avatar
      Kevin Dooley

      Hi Richard,

      The method shown in the blog post should accomplish what you’re asking. I suggest putting the offending /24 range in the “SuspiciousRanges” object group. Since the ACL includes this line:

      access-list outside_in extended deny ip object-group SuspiciousRanges any

      Note that the syntax specifies the source address (the object-group) followed by the destination address (any). You need them in this order because we are blocking *inbound* traffic to the firewall. Then all you need to do is add the /24 subnet to that object-group (you can add as many network-object lines as you like to the object-group). Using my favourite North Korean subnet as an example:

      object-group network SuspiciousRanges

      The other thing to check is that you have applied the ACL properly to inbound traffic received on the “outside” interface of your firewall. I always label this interface as “outside”, but if you’ve called it something else, you’ll need to apply that name in this command:

      access-group outside_in in interface outside

      I hope this helps.

  2. Gil Avatar


    Great article. I started looking into blocking entire countries as you mentioned. North Korea alone would require over 1200 lines in an ACL. Is there a better way to this? If not how can you manage those large ACL’s?

    1. Kevin Dooley Avatar
      Kevin Dooley

      Hi Gil:

      According to the geolocation databases that I’ve seen, North Korea has only one official range, It has also been reported that they are using the China Telecom range, So blocking them only requires 2 lines in the ACL.

      The most efficient way to handle geographical blocking in a Cisco firewall is actually to use the FirePower IDS/IPS and subscribe to the Cisco geolocation service. It will reach out to a web service and download the latest tables. Then you can configure the geolocation blocking rules in FirePower and they will automatically stay up to date.

      I once had a client whose customers were all in North America. They felt that there was no legitimate reason for anybody outside of that geographical region would ever want to reach their network. So we configured a geolocation white list of allowed countries and blocked everything else. This, of course, became unworkable the day the CEO went on holiday in Europe.

      The truth is that serious attackers will generally bounce through several intermediate devices to hide their traces. In my experience, most attacks seem to come from either residential broadband connections or commercial web hosting facilities. In both cases, it’s probably safe to assume that the attackers are using VPN proxies and farms of compromised systems wherever they find them, including within your own country. So I think geolocation on Internet firewalls is a little less useful these days than it once was.

      But the answer to the other part of your question, how to manage large ACLs, is to use object-groups. I will often create an object-group called something like “GlobalBlackList” and fill it with address ranges that have annoyed me. I like to create separate object-groups for everything that I want to explicitly allow or block. If I am using an external email proxy, and that’s the only device that’s allowed to make inbound email connections, it goes in an object-group. If there is an external partner from whom we accept inbound connections, put it in an object-group. A service for DNS zone transfers. A VPN to AWS. Then, if any of those addresses change, it’s very easy to find the thing you need to change, and that change can be done without affecting any other production traffic.

  3. Waqar Avatar

    How can i check rule count on each interface ?

    1. Kevin Dooley Avatar
      Kevin Dooley

      Hi Waqar: The “show access-list” command gives counts of hits on the ACL. Usually I put different ACLs on different interfaces, so the counters are always specific to the interface where they are applied. Does that answer your question or did you have something else in mind?

      1. Chris Birdsong Avatar
        Chris Birdsong

        I have been trying to take an any any rule and build an ACL that accomplishes what you preach above and can see the hits on the entry but is there a way to see what those hits are specifically? Can I identify that traffic so I can then permit or deny it accordingly?

  4. dusty Avatar

    Hi Kevin,

    Could you suggest a list of commands for operational troubleshoot. In multi vendor environment is bit difficult to remember everything. I am working on making a database and certainly can use you insight on ASA.

Leave a Reply

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