Cisco IOS Router Firewall Configuration (and close an open DNS resolver)

So I was happily using my Cisco 861W router, and then all of a sudden, years later my internet connection started to slow down. It was fairly obvious the problem was happening during domain name resolution. Sometimes it was fast, other times it was slow. To diagnose the problem, I turned on debugging:

debug domain
terminal monitor

What I then saw was a flood of DNS requests that were obviously not mine. They were targeting some web server in China. My first thought was that the WiFi had been breached, but after confirming the WiFi was secure, it became obvious that what happened was that I had unwittingly setup an open dns resolver and it was being exploited. It was being hit with so much traffic that it was slowing down and even dropping my legitimate DNS queries.

So what is an open DNS resolver? Many of us configure our Cisco routers to function as a DNS server (in IOS speak, “ip dns server”). This lets us point the machines on our LAN to the router, and the router will go off and resolve DNS queries for us. What I didn’t know, and what isn’t discussed in many howtos, is that doing this opens the DNS server not just on the LAN port, but also the WAN port. Anyone on the Internet can connect to your router’s DNS server and use it to resolve a query. Attackers love this, as it allows them to mount a distributed denial of service attack. They find all of the open resolvers they can, and submit queries to them, which in turn hits someones DNS server very hard. This is bad.

So, then I launched into a multi-hour middle-of-the-night trek to figure out how to secure the Cisco router. I discovered ZBF, Cisco’s Zone Base Firewall. For a Cisco novice like me, it was quite difficult to figure out how to configure this thing from the command line. So, I’ll share what I learned.

Let’s start by dumping all of the new commands together and then we’ll go through certain parts in more detail:

ip access-list extended OUTSIDE_DHCP
    permit udp any any eq 68
object-group network og-L1-DNS-Servers
    description Allowed external DNS servers
    host 75.75.75.75
    host 75.75.76.76
ip access-list extended OUTSIDE_DNS
    permit udp object-group og-L1-DNS-Servers eq domain any
!
class-map type inspect match-all CLASS_DNS
 match protocol dns
class-map type inspect match-all CLASS_ICMP
 match protocol icmp
class-map type inspect match-all CLASS_PPTP
 match protocol pptp
class-map type inspect match-any CLASS_GRE
 match access-group name ACL_GRE
class-map type inspect match-all CLASS_UDP
 match protocol udp
class-map type inspect match-all CLASS_TCP
 match protocol tcp
!
class-map type inspect match-all CLASS_OUTSIDE_DNS
!   match class-map CLASS_DNS
   match access-group name OUTSIDE_DNS
!
class-map type inspect match-all CLASS_OUTSIDE_DHCP
   match access-group name OUTSIDE_DHCP
!
policy-map type inspect POLICY_INSIDE_TO_OUTSIDE
 class type inspect CLASS_PPTP
  inspect
 class type inspect CLASS_GRE
  pass
 class type inspect CLASS_TCP
  inspect
 class type inspect CLASS_UDP
  inspect
 class type inspect CLASS_ICMP
  inspect
 class class-default
  drop log
policy-map type inspect POLICY_OUTSIDE_TO_INSIDE
 class type inspect CLASS_GRE
  pass
 class type inspect CLASS_OUTSIDE_DNS
  pass
 class class-default
  drop log
policy-map type inspect POLICY_OUTSIDE_TO_SELF
 class type inspect CLASS_OUTSIDE_DNS
  pass
 class type inspect CLASS_OUTSIDE_DHCP
  pass
 class type inspect CLASS_ICMP
  inspect
 class class-default
  drop log
!
zone security INSIDE
zone security OUTSIDE
zone-pair security INSIDE_TO_OUTSIDE source INSIDE destination OUTSIDE
 service-policy type inspect POLICY_INSIDE_TO_OUTSIDE
zone-pair security OUTSIDE_TO_INSIDE source OUTSIDE destination INSIDE
 service-policy type inspect POLICY_OUTSIDE_TO_INSIDE
zone-pair security OUTSIDE_TO_SELF source OUTSIDE destination self
 service-policy type inspect POLICY_OUTSIDE_TO_SELF
!
interface FastEthernet4
   zone-member security OUTSIDE
!
interface Vlan1
   zone-member security INSIDE
!
parameter-map type inspect LONG_TCP_IDLE
 tcp idle-time 604800
!
policy-map type inspect POLICY_INSIDE_TO_OUTSIDE
   class type inspect CLASS_TCP
       inspect LONG_TCP_IDLE

Now let’s pick this apart in sections:

ip access-list extended OUTSIDE_DHCP
    permit udp any any eq 68
object-group network og-L1-DNS-Servers
    description Allowed external DNS servers
    host 75.75.75.75
    host 75.75.76.76
ip access-list extended OUTSIDE_DNS
    permit udp object-group og-L1-DNS-Servers eq domain any
!

The above object-group and access-list directives set up ACLs for allowing DHCP and DNS traffic. The DNS resolvers that my ISP provides are 75.75.75.75 and 75.75.76.76. A lot of people use Google’s resolver at 8.8.8.8 so that might be useful as well. Even though we defined the ACLs, we aren’t actually using them yet. We need to create class-maps and policy-maps. Let’s make some class-maps, starting with the obvious easy ones:

class-map type inspect match-all CLASS_DNS
 match protocol dns
class-map type inspect match-all CLASS_ICMP
 match protocol icmp
class-map type inspect match-all CLASS_PPTP
 match protocol pptp
class-map type inspect match-any CLASS_GRE
 match access-group name ACL_GRE
class-map type inspect match-all CLASS_UDP
 match protocol udp
class-map type inspect match-all CLASS_TCP
 match protocol tcp

The above setup a whole bunch of class-maps that match various protocols. For example, CLASS_TCP will match TCP traffic. Now we need to make some more interesting class maps for our DNS and DHCP traffic:

class-map type inspect match-all CLASS_OUTSIDE_DNS
!   match class-map CLASS_DNS
   match access-group name OUTSIDE_DNS
!
class-map type inspect match-all CLASS_OUTSIDE_DHCP
   match access-group name OUTSIDE_DHCP

The above setup class-maps to handle DNS and DHCP according to the access lists that we defined earlier. Now we need to make some policy maps. Policy maps are an ordered series of steps. It starts with the first one. If that matches then it does whatever the first one says to do. If it doesn’t match, then it goes to the next one, and so on.

The three options you have are ‘drop’, ‘pass’, and ‘inspect’. Drop and pass are relatively self-explanatory: drop immediately drops the packet and pass will forward the packet. The ‘inspect’ directives tell the Cisco firewall to inspect the contents of these packets and do whatever magic it needs to do to accomplish stateful packet inspection (I’m a little sketchy on what exactly this magic is; I’m guessing in the case of a TCP packet, it sets up the appropriate state to allow the incoming packets back in)..

policy-map type inspect POLICY_INSIDE_TO_OUTSIDE
 class type inspect CLASS_PPTP
  inspect
 class type inspect CLASS_GRE
  pass
 class type inspect CLASS_TCP
  inspect
 class type inspect CLASS_UDP
  inspect
 class type inspect CLASS_ICMP
  inspect
 class class-default
  drop log

The policy-map for POLICY_INSIDE_TO_OUTSIDE specifies all the kinds of traffic that we want to permit from inside our lan to the outside world. For example, we want TCP so we can use the web. We want ICP so we can ping stuff. The last action is ‘drop log’ which tells the router to both drop any packet that reaches that point, and to log it. If you look at the log (“terminal monitor”) then you can see packets that are dropped, and why. Now let’s go on to the next policy map:

policy-map type inspect POLICY_OUTSIDE_TO_INSIDE
 class type inspect CLASS_GRE
  pass
 class type inspect CLASS_OUTSIDE_DNS
  pass
 class class-default
  drop log

The policy-map for POLICY_OUTSIDE_TO_INSIDE specifies the traffic we want to allow from the outside world back into our LAN. This is where you would put rules for port forwarding to servers that we wanted to be reachable from the outside. Examples would be if we wanted to run a publicly-accessible web server on our LAN, or we wanted to be able to SSH from the outside world into machines on our LAN. I’m not sure CLASS_OUTSIDE_DNS is necessary here. I put it in while I was troubleshooting this whole mess, and I’m too gun shy (or too lazy) to take it back out and see if everything still works. You can probably safely drop CLASS_OUTSIDE_DNS from this policy map.

The next policy map is the interesting one because it finally addresses the policy that we wanted to solve:

policy-map type inspect POLICY_OUTSIDE_TO_SELF
 class type inspect CLASS_OUTSIDE_DNS
  pass
 class type inspect CLASS_OUTSIDE_DHCP
  pass
 class type inspect CLASS_ICMP
  inspect
 class class-default
  drop log

The above policy-map tells what traffic is allowed to reach the services running inside the router. The router is going to send out a couple of important things: DNS requests and DHCP requests (assuming your router configures itself to your ISP via DHCP). This is where we close up our open DNS resolver. Previously we allowed any old request on the Internet to reach our server. Here we restrict it to CLASS_OUTSIDE_DNS and CLASS_OUTSIDE_DHCP.

CLASS_OUTSIDE_DNS was the class-map that we setup to use the ACL which specified our ISP’s DNS servers. Effectively we’re saying “only allow outside DNS packets to reach the router’s DNS server if they are from 75.75.75.75 or 75.75.76.76”. CLASS_OUTSIDE_DHCP permits all UDP traffic on port 68 to reach the router’s DHCP client. This is important as my router configures itself via DHCP.

Note: If you want your router to be SSHable from the outside world, then POLICY_OUTSIDE_TO_SELF is where you’d need to address that. Much like how we enabled DHCP traffic on port 68, you would want to enable SSH traffic on port 22. Note that I’m speaking of SSHing to the router (i.e. for remote management) not SSHing to machines on your LAN. SSHing to stuff on the LANs would be covered by POLICY_OUTSIDE_TO_INSIDE.

Now it’s time for us to define some zones:

zone security INSIDE
zone security OUTSIDE
zone-pair security INSIDE_TO_OUTSIDE source INSIDE destination OUTSIDE
 service-policy type inspect POLICY_INSIDE_TO_OUTSIDE
zone-pair security OUTSIDE_TO_INSIDE source OUTSIDE destination INSIDE
 service-policy type inspect POLICY_OUTSIDE_TO_INSIDE
zone-pair security OUTSIDE_TO_SELF source OUTSIDE destination self
 service-policy type inspect POLICY_OUTSIDE_TO_SELF

We create two zones, INSIDE and OUTSIDE. “self” must be one that we get for free without having to define it. We then tell the router to use POLICY_INSIDE_TO_OUTSIDE when the inside wants to talk to something on the outside, POLICY_OUTSIDE_TO_INSIDE when the outside wants to talk to something on the inside, and POLICY_OUTSIDE_TO_SELF when something on the outside wants to talk to the router itself.

Next up we have to tell it which ports are INSIDE and which ports are OUTSIDE:

!
interface FastEthernet4
   zone-member security OUTSIDE
!
interface Vlan1
   zone-member security INSIDE
!

These are of course going to depend on your brand of Cisco router. My 861W uses FastEthernet5 for its WAN port, so we set that as OUTSIDE. Vlan1 represents the inside, so we set that as INSIDE.

Now for one final wrinkle. I found that my ssh sessions would always disconnect after being idle for 60 minutes. That was annoying. I have ssh sessions open for days. So I added these two rules:

parameter-map type inspect LONG_TCP_IDLE
 tcp idle-time 604800
!
policy-map type inspect POLICY_INSIDE_TO_OUTSIDE
   class type inspect CLASS_TCP
       inspect LONG_TCP_IDLE

The parameter-map sets parameters for the inspect rules. Recall that the ‘inspect’ directives inside the policy maps told the router to do whatever magic it does inspecting packets and maintaining state. The default is to time out TCP packets after 60 minutes of idleness. So what we do here is define a new policy map that specifies a whole week of idleness, and then apply that parameter map to the inspect directive for CLASS_TCP traffic. Now I can leave ssh sessions idle for a good long time.

Leave a Reply

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