Sunday, April 28, 2013

Zone Based Firewall

Zone Based Firewall (ZFW) is an upgrade to the CBAC inspection engine, allowing firewalling on IOS.  The notable improvement is that rules are arranged by zone instead of by interface, allowing for better scalability.

The basic hierarchy is as follows:
  - class-maps match traffic
  - policy-maps match class-maps and define an action
  - interfaces are assigned a zone
  - zone-pairs match a directional flow between two zones
  - policy-maps are assigned to zone-pairs

That may sound like a lot, but it's actually quite simple.  The syntax is very similar to MQC QoS syntax, so picking it up on a basic level is quite easy.

Here's a basic example to permit icmp traffic initiated from INSIDE towards OUTSIDE.  For our lab, I'll be using a GNS3 3725 12.4(15)T, with two Virtualbox Linux hosts:


R1:
zone security INSIDE
zone security OUTSIDE

interface Fa0/0
  zone-member security OUTSIDE

interface Fa0/1
  zone-member security INSIDE

class-map type inspect ICMP-TRAFFIC
  match protocol icmp

policy-map type inspect INSIDE-TO-OUTSIDE
  class type inspect ICMP-TRAFFIC
    inspect

zone-pair security INSIDE-TO-OUTSIDE source INSIDE destination OUTSIDE
  service-policy type inspect INSIDE-TO-OUTSIDE

Let's break this down a little.

The "zone security INSIDE" and "zone security OUTSIDE" are just text labels.  "INSIDE" and "OUTSIDE" could be any string, and they're just used to group interfaces together.  We then assign them to interfaces.  On 12.4T, interfaces of the same security zone can pass traffic, and interfaces with different zones cannot.  I read that on 15.x releases, interfaces within the same zone, by default, also cannot pass traffic, although I haven't tested this.

It's of key note that once you assign an interface to a zone, by default, it doesn't communicate with any other zone except "self", which we'll address down the line.  So if you have an interface with no zone whatsoever, and an interface in INSIDE, INSIDE will not pass traffic to the un-zoned interface. Nor will it pass traffic to OUTSIDE, or any other zone, until configured to do so.  So, apply carefully!

class-map type inspect ICMP-TRAFFIC
  match protocol icmp

This matches the ICMP protocol.  Let's just go with that for now, I'll get into much more detail on this topic later.

policy-map type inspect INSIDE-TO-OUTSIDE
  class type inspect ICMP-TRAFFIC
    inspect

Similar to QoS, the policy-map matches the class-map and defines an action.  We'll define the action as "inspect", which I will cover momentarily, as part of the firewall primer.

"zone-pair security INSIDE-TO-OUTSIDE source INSIDE destination OUTSIDE"

This defines a one-way pairing of zones to apply rules to.  "source INSIDE destination OUTSIDE" is not the same as "source OUTSIDE destination INSIDE".

"service-policy type inspect INSIDE-TO-OUTSIDE"

Then we apply the policy-map's matches & actions to the zone pairing.

In short, this will allow ICMP traffic from INSIDE towards OUTSIDE, as well as OUTSIDE's response.  However, OUTSIDE would not be able to initiate a ping towards INSIDE.

I normally try to keep my posts on the highly technical side, generally discussing the matters I found confusing.  In this case, since my blog is geared towards the CCIE R&S, I'm going to give a brief firewall primer.  I know when I first got into route/switch I was almost exclusively a route/switch tech, I barely ever touched a PIX or an ASA; now I work on them daily. Those of you with firewall experience outside IOS can skip this part.  In addition, I am going to give an ASA-to-ZFW comparison below that, which you may want to read.

Firewall Primer (skip if you're already familiar with layer 4 firewalling)
The basic function of a layer 4 firewall is to allow certain interfaces to initiate traffic towards other interfaces, and to permit that interface's response traffic.  This is done by identifying acceptable traffic and then applying stateful traffic inspection.

Stateful traffic inspection is the action of looking at the layer 4 header, and creating a temporary "ACL" in the reverse direction, allowing the traffic.  In addition, in the case of TCP, traffic is normally given sanity checks:  Is the traffic arriving in an acceptable timeframe?  Are the packets in the right order?

Let's say you want to be able to RDP from INSIDE to a system on the OUTSIDE interface.  The server you're connecting to is in the OUTSIDE zone on 10.0.0.50.  The client is on 192.168.0.5 in INSIDE.  Normal ACLs are not ideal for this.  The source TCP port on 192.168.0.5 will change during every connection.  The destination (3389) will remain the same, so an ACL from INSIDE to OUTSIDE would be easy, but the return traffic (3389 -> random source port) is tricky.  We could permit every TCP port on 192.168.0.5 from 10.0.0.50, but that would defeat the security we'd like to have.  Instead, having the firewall watch our random source port, and dynamically permit the return traffic back in, and then closing the dynamic permit after a timer has expired or after a TCP FIN is seen, is ideal.

RDP is a reasonably simple protocol, and simply monitoring the layer 4 header and dynamically opening the return port can do the job.  However, If you've tried to deal with FTP or SIP in an ACLed and NATed environment, you'll understand that these scenarios get far more complex.  FTP is much easier to lab up than SIP, so we'll be focusing on FTP.  FTP initially connects on port 21 but then opens a secondary data channel.  This port is typically dynamically negotiated on the control channel (over port 21), so "deep packet" application-level inspection is required so that the firewall can figure out what traffic flow needs to be allowed from OUTSIDE back to INSIDE.  This method, in Cisco terms, is referred to as either a "fixup" (old PIX terminology) or an "inspect" (modern lingo).

The inspect will look at the application-specific traffic, including embedded IPs and ports, and dynamically allow the necessary traffic flows through the firewall.

To summarize, these are the three main functions of the modern firewall:
1) Stateful traffic inspection to open reverse traffic flow from a trusted to an untrusted interface
2) Stateful traffic inspection for TCP sanity checks to prevent the target from receiving invalid TCP traffic intended to exploit a problem with the host.
3) Fixup/Inspects to allow complex protocols to cross between zones.

ASA-to-ZFW comparison (skip this if you're not familiar with the ASA)
ZFW has much in common with the ASA, and also a lot of differences.

Similarities:
- Zones are similar.  The major difference in ZFW on 12.4T is that two or more interfaces in the same zone can pass traffic between each other.
- The basic concepts of permitting traffic from trusted -> untrusted, and then permitting the return traffic from untrusted -> trusted, are the same.

Differences:
- There are no security levels.  Every interface is assumed default-deny to every other interface until you permit traffic
- ACLs can't be applied directly to a zone, you have to apply them through the MQC method.
- ZFW has a "self" interface concept.  It shares a few concepts with a management interface from the ASA, but really it's a different beast.  I assume the thought process is that IOS is primarily geared towards being a router, and having to manually permit OSPF, BGP, etc would be an unnecessary headache in ZFW deployment.  More on the "self" interface later.
- Fixups/Inspects are integrated with permitting traffic. 

Back to ZFW....
Most of the magic in ZFW happens in the class-maps and policy-maps, so let's take a look at the options there, and lab a sample scenario.

class-maps can match protocols and ACLs.  A basic example, if you wanted to match all FTP traffic sourced from 192.168.0.2 towards 10.0.0.2, would be:

ip access-list extended sample_traffic
  permit ip host 192.168.0.2 host 10.0.0.2

class-map type inspect match-all ftp_sample
 match access-group name sample_traffic
 match protocol ftp

Note the match-all statement.  This is default, if you just type "class-map type inspect ftp_sample" you get match-all.  There are scenarios where match-any is more desirable, and I've accidentally black-holed traffic in production in the past due to this error.

A class-map can also match another class-map.  The reason for nested class-maps if you need to mix a match-any and a match-all.  Let's say you wanted to match traffic from 192.168.0.2 towards 10.0.0.2 for either ICMP or FTP.  This is the easiest way to accomplish it:

ip access-list extended sample_traffic
  permit ip host 192.168.0.2 host 10.0.0.2

class-map type inspect match-any some_protocols
  match protocol ftp
  match protocol icmp

class-map type inspect match-all ftp_icmp_sample
 match access-group name sample_traffic
 match class-map some_protocols

This would match 192.168.0.2 towards 10.0.0.2 for either ICMP or FTP.  The match-all says the ACL must match, plus one of protocols inside the match-any.

Policy-maps define the action taken on the traffic matched in the class-map.  There are only a few actions that can be defined, but as we'll see, some of them have a hidden agenda.

The options are:
  drop - obvious, drop the packet
  inspect - stateful inspection engine.  This is the most common match.
  pass - allow the packet, but don't perform stateful inspection; return traffic is not permitted
  police - rate limiting, must be used in conjunction with inspect.
  service-policy - Adds additional application-specific inspections
  urlfilter - This requires a filtering server to operate, and is beyond the scope of this document

We'll start with a scenario for drop, pass, and inspect.

Let's build a config that allows:
 - All traffic from inside to outside except telnet
 - FTP from inside to outside, including dynamic ports
 - SNMP traps from outside to inside, without using inspect

ip access-list extended snmp-trap
 permit udp host 10.0.0.2 host 192.168.0.2 eq 162  ! traps are normally on UDP 162

class-map type inspect match-all telnet
 match protocol telnet

class-map type inspect match-all snmp-trap
 match access-group name snmp-trap

class-map type inspect match-any all_traffic
 match protocol ftp  ! ordering is important
 match protocol tcp
 match protocol udp
 match protocol icmp

policy-map type inspect inside-to-outside
 class type inspect telnet
  drop
 class type inspect all_traffic
  inspect
 class class-default

policy-map type inspect outside-to-inside
 class type inspect snmp-trap
  pass
 class class-default

zone security INSIDE
zone security OUTSIDE

zone-pair security INSIDE-TO-OUTSIDE source INSIDE destination OUTSIDE
 service-policy type inspect inside-to-outside

zone-pair security OUTSIDE-TO-INSIDE source OUTSIDE destination INSIDE
 service-policy type inspect outside-to-inside

interface FastEthernet0/0
 zone-member security OUTSIDE

interface FastEthernet0/1
 zone-member security INSIDE

Now let's test!

from 192.168.0.2 -> 10.0.0.2



ICMP working...  how's about that telnet we blocked?



Yup, not working, as expected.

FTP was hard to get in one screen grab, but here's a directory listing and a file transfer.



UDP 162 from outside to inside is a bit tricky to test - I was specifically looking for a flow that would work unidirectionally.  But let's at least make sure it can't access back inside.



Outside -> inside working as expected.

I pointed out earlier that ordering is important -

class-map type inspect match-any all_traffic
 match protocol ftp  ! ordering is important
 match protocol tcp
 match protocol udp
 match protocol icmp

This statement permits FTP with fixup, and then does a layer 4 SPI on tcp/udp/icmp.  What happens if we change the order?

class-map type inspect match-any all_traffic
 match protocol tcp
 match protocol udp
 match protocol icmp
 match protocol ftp



We can still log in to the control channel (port 21), but the data channel can't open, which is apparent from the directory listing failing.

This behavior is tied to the ordering of the list:
 match protocol tcp
 match protocol udp
 match protocol icmp
 match protocol ftp

In this case, the generic TCP match hits before the ftp.  The generic TCP match allows 21 outbound and the return traffic, but without specifically inspecting the FTP protocol, the data channel cannot open.  Always match your more-specific protocols before your less-specific protocols.

Another, far easier way, to accomplish the same thing, is in this fashion:

ip access-list extended permit-all
  permit ip any any

class-map type inspect match-any new_all_traffic
  match access-group name permit-all

policy-map type inspect inside-to-outside
 class type inspect telnet
  drop
 class type inspect new_all_traffic
  inspect

An oddity to ZFW is that if you don't specify a protocol in a class-map, all protocols are inspected, and all possible fixups are applied.  In fact, even entering this config produces:

%No specific protocol configured in class new_all_traffic for inspection.  All protocols will be inspected

Now let's try that FTP again.



And it works again!

You've probably noticed the class-default that each of these policy-maps ends in.  Predictably, this is a default-drop, but it can be changed to a default pass, or, contrary to what the documentation says, on 12.4(15)T14, you can define a default inspect, as well.  If you do leave it as default drop, no icmp messages are sent for dropped traffic.

In addition, if you want to log drops on the class-default, that can be configured as "drop log", as opposed to just the implicit drop.  Note "drop log" is available in any class, but the logging only works in class-default. If you want to log another class, you use parameter maps, which we'll get to shortly.

Now let's add policing to the same scenario:

policy-map type inspect inside-to-outside
 class type inspect telnet
  drop
 class type inspect all_traffic
  inspect
  police rate 8000 burst 1000
 class class-default

That's all the options you get - rate and burst.  No remarking, etc.
Note that it only works in conjunction with inspect actions.  Drop would have no point, and it's not allowed on pass.

I can't say I care for the way Cisco built the "protocol" system inside ZFW.  If you do a "match protocol ?", you get a very, very long list.  Some of these, such as FTP and SIP, obviously imply a fixup - deep packet inspection and modification, to deal with embedded IPs and ports.  Others would have no reason to have a fixup.  For example, what fixup would you apply to SSH?  Sadly, I can't find any documentation anywhere that says which are simply macros (making it easy to match on a port) and those that are a macro plus a fixup.  The list of protocols is far too long and lists far too many obscure protocols to walk the list and test it, so to some extent, applying the protocol inspection is a crapshoot - which ones will modify traffic and which ones will not?

On a similar topic, what if you wanted to match a protocol on a port that it's not normally mapped to?  If you wanted to inspect HTTP on port 8080, you need to tell IOS to do it.  Fortunately it's easy:

ip port-map http port tcp 8080

Optionally, you can attach an ACL to it, so that the port-map is only valid on certain flows.

In addition, you can create your own protocols, but they must being with the prefix "user":

ip port-map user-rdp port tcp 3389

Let's pause and take a look at what, in my opinion, is the best show command for ZFW:

show policy-map type inspect zone-pair

The output will give you tons of information, so I will only show inside-to-outside as a sample:

R1#show policy-map type inspect zone-pair
 Zone-pair: INSIDE-TO-OUTSIDE
  Service-policy inspect : inside-to-outside
    Class-map: telnet (match-all)
      Match: protocol telnet
      Drop
        0 packets, 0 bytes
    Class-map: all_traffic (match-any)
      Match: protocol tcp
        3 packets, 120 bytes
        30 second rate 0 bps
      Match: protocol udp
        0 packets, 0 bytes
        30 second rate 0 bps
      Match: protocol icmp
        2 packets, 128 bytes
        30 second rate 0 bps
      Match: protocol ftp
        0 packets, 0 bytes
        30 second rate 0 bps
      Inspect
        Packet inspection statistics [process switch:fast switch]
        tcp packets: [44:16]
        icmp packets: [0:906]
        Session creations since subsystem startup or last reset 5
        Current session counts (estab/half-open/terminating) [0:0:0]
        Maxever session counts (estab/half-open/terminating) [2:1:1]
        Last session created 01:59:10
        Last statistic reset never
        Last session creation rate 0
        Maxever session creation rate 3
        Last half-open session total 0
    Class-map: class-default (match-any)
      Match: any
      Drop (default action)
        54 packets, 0 bytes

You get session details, match details per class, drop details, etc.  Almost too much to comment on!

I mentioned the "self" zone at the beginning of the document.  By default, all traffic to and from the router's control plane is permitted.  This makes sense for a device that's a router first and a firewall second.  We wouldn't want OSPF or BGP being inadvertently blocked. 

If you do want to control access in and out of the control-plane, you use the self zone.  It's quite easy to understand:

class-map type inspect match-all bgp
 match protocol bgp

policy-map type inspect outside-to-self
 class type inspect bgp
  pass
 class class-default

zone-pair security OUTSIDE-TO-SELF source OUTSIDE destination self
 service-policy type inspect outside-to-self

This policy would only allow BGP sessions from outside to self, but it would still allow self-to-anything, and any other interface (such as inside) to self.  The only potentially gotcha is that while the router's control plane would still be able to initiate connections towards outside, since no inspect is in place from self to outside, outside can't reply (unless, in the example above, the reply is BGP).

Dealing with NAT is a must for any firewall.  Fortunately, ZFW does a good job.  It handles PAT overloads and 1:1 NATs without any trouble.  Let's put NAT in between our inside and outside, and re-test FTP, this time as "inside" hosting an FTP server, and outside attempting to access it.

interface fa0/0
 ip nat outside

interface fa0/1
 ip nat inside

ip nat inside source static 192.168.0.2 10.0.0.5

ip access-list extended ftpserver
 permit ip any host 192.168.0.2  ! Note the inside address is the one to permit, not the outside

class-map type inspect match-all ftpserver
 match access-group name ftpserver
 match protocol ftp

policy-map type inspect outside-to-inside
 class type inspect ftpserver
  inspect

Similar to ASA 8.4, ZFW takes the "real IP" approach when permitting NAT traffic.  Rather than permitting the outside global IP (10.0.0.5), you permit the inside IP address (192.168.0.2), and ZFW figures out the translation itself.

Now let's test from outside to inside.



And it works - the FTP inspection permitted the traffic, dynamically swapped the embedded IPs to deal with NAT (192.168.0.2 needs to be changed to 10.0.0.5 inside the control channel), and opened the appropriate ports.  Impressive.  Back to my earlier concerns, I wish all this behind the scenes magic was documented by Cisco somewhere. 

In addition to what we've already seen, ZFW can also limit quantity of sessions for a particular protocol, change logging options, and use its own version of TCP intercept.  All of these features are implemented through parameter-maps.

Let's start with logging.

As we saw earlier, the class-default drop has a simple way to log.  If you want to log on other traffic classes, you'd do the following:

parameter-map type inspect logging
  audit-trail on
 sessions maximum 2147483647

policy-map type inspect inside-to-outside
 class type inspect all_traffic
  inspect logging

The "sessions maximum" command is a default filled in if you don't pick a parameter.

and you can expect output such as:

*Mar  2 07:57:10.348: %FW-6-SESS_AUDIT_TRAIL_START: (target:class)-(INSIDE-TO-OUTSIDE:all_traffic):Start icmp session: initiator (192.168.0.2:8) -- responder (10.0.0.2:0)

*Mar  2 07:57:24.548: %FW-6-SESS_AUDIT_TRAIL: (target:class)-(INSIDE-TO-OUTSIDE:all_traffic):Stop icmp session: initiator (192.168.0.2:8) sent 280 bytes -- responder (10.0.0.2:0) sent 280 bytes

If you happen to know a server behind the firewall can only handle 20 sessions before it has performance problems, to prevent a denial of service, you can limit how many sessions hit it.  Using our earlier policy-map for permitting ftp in behind our NAT, let's only permit 20 logins:

parameter-map type inspect outside-to-inside
 sessions maximum 20

policy-map type inspect outside-to-inside
 class type inspect ftpserver
  inspect outside-to-inside

And last, ZFW has it's very own TCP intercept-like function.

To prevent DOS attacks, you may want to implement TCP Intercept.  The complete functionality of TCP Intercept is beyond the scope of this document, but here is a good link to the details of traditional TCP Intercept: http://sakshamdixit.com/?p=241

A sample parameter map for ZFW's version of TCP intercept is as follows:

parameter-map type inspect INTERCEPT
 max-incomplete low 500
 max-incomplete high 700
 one-minute low 12
 one-minute high 70
 sessions maximum 5000

Another advanced feature is Application Inspection and Control, or AIC.  Many documents refer to this as "layer 7 inspection" for ZFW, which is true, but what's that mean for all the fixup-oriented protocols we already looked at?  I suppose one could argue that swapping IPs and ports in an FTP or SIP packet is really layer 5 (session) inspection, not layer 7.  This goes back to my earlier discomfort with match protocol.  Some of those inspections are in fact layer 4, some are layer 5+. 

My concerns on terminology aside, AICs give you some very deep, application-level control on specific protocols.  The protocols supported are limited to:

  aol        AOL Instant Messenger
  edonkey    Torrent File Sharing
  fasttrack  Torrent File Sharing
  gnutella   Torrent File Sharing
  http       HTTP
  imap       IMAP
  kazaa2     Torrent File Sharing
  msnmsgr    MSN Instant Messenger
  pop3       POP3
  smtp       SMTP
  sunrpc     RPC
  ymsgr      Yahoo Instant Messenger

Each one of these could probably get a blog onto itself, so we're going to focus on the HTTP inspection.

You start by building a class-map of "type inspect http":

class-map type inspect http match-any deephttp

Your first-level options are:

R1(config-cmap)#match ?
  req-resp  HTTP request or response
  request   HTTP request
  response  HTTP response

You can match request or response, request, or just response.  If your policy matches from the side requesting towards the side responding, you might question whether or not you can even match "response".  After all, at that point in the game, you've already permitted the return traffic.  As we'll see, it does, in fact, still work under that scenario.

Let's create a policy to drop traffic if the response body contains the text "dangerous content", but we'll filter from the requesting side.  The server will be at 192.168.0.2, NATed to 10.0.0.5, and the requestor will be 10.0.0.2.  So we will be matching from OUTSIDE to INSIDE.

There's a lot of pieces to this, so I'm going to walk through the example one step at a time.

We had this access list earlier, but just as a reminder that it exists:

ip access-list extended outside_to_inside
 permit ip host 10.0.0.2 host 192.168.0.2

We're going to need to make a different kind of parameter map this time, one that matches regex.  Since I want to specifically match "dangerous content" anywhere in the body, we don't use any "regex" here, it's just a text string:

parameter-map type regex danger
 pattern "dangerous content"

This is the actual definition for the body/response regex match:

class-map type inspect http match-any deephttp
 match  response body regex danger

AICs need to be matched as child policies to a parent of the appropriate master type.  With HTTP, the obvious parent is HTTP.  I also match the ACL we discussed above, to describe the outside-to-inside flow:

class-map type inspect match-all parent-http
 match protocol http
 match access-group name outside_to_inside

Then we create the actual AIC policy:

policy-map type inspect http nested_http
 class type inspect http deephttp
  reset  ! Send a TCP reset
  log    ! log the violation

Now we'll apply the class to the pre-existing & pre-applied outside-to-inside policy-map

policy-map type inspect outside-to-inside
 class type inspect parent-http
  inspect
  service-policy http nested_http

And now we test how it works.  I have two html files on 192.168.0.2, one is index.html, which is, by our definition, "safe".  The other is sketchy.html, which is dangerous.



As we see, we can get index.html with no problems.
Now for sketchy.html:



We didn't get very far that time.  What's the router got to say about it?



Reset and logged, as expected!

And the very last topic, ZFW as a transparent firewall.  It's not as scary as it sounds - build a BVI, and otherwise the configuration is almost identical.

bridge irb
bridge 1 protocol ieee
bridge 1 route ip

interface BVI1
 no ip address
end

interface FastEthernet0/0
 no ip address
 bridge-group 1

interface FastEthernet0/1
 no ip address
 bridge-group 1

As you can see, the ZFW config has been temporarily removed, the IP addresses are gone, and we are in a pure bridged environment.  I've wiped out the rest of the ZFW config, so we'll be starting from scratch.

I've changed the 192.168.0.2 Linux VM to 10.0.0.6.
I've verified bidirectional communication prior to implementing the firewall config.

Let's allow http from 10.0.0.6 to 10.0.0.2 and block all other protocols.

zone security INSIDE  ! will be assigned to the 10.0.0.6 interface
zone security OUTSIDE  ! will be assigned to the 10.0.0.2 interface

ip access-list extended ip-flow
 permit ip host 10.0.0.6 host 10.0.0.2

class-map type inspect match-all web-traffic
 match access-group name ip-flow
 match protocol http

policy-map type inspect allow-http
 class type inspect web-traffic
  inspect
 class class-default

interface FastEthernet0/0
 zone-member security OUTSIDE

interface FastEthernet0/1
 zone-member security INSIDE
end

zone-pair security INSIDE-TO-OUTSIDE source INSIDE destination OUTSIDE
 service-policy type inspect allow-http



No major changes to the way things work - we get layer 4+ inspection across the BVI in the same fashion we're used to.

Hope you enjoyed,

Jeff

3 comments:

  1. Thank you, thank you, thank you!

    you are a legend sir, i have been serching for an answer to the self zone issues i have been having for a very long time you you clearly stated it.

    i had a ZP for self to out which caused all sorts of problems, not realising without it all traffic is permitted from self to anywhere, so now i can just pass inbound bits and bobs as needed,

    again...THANK YOU :)

    ReplyDelete
    Replies
    1. Thanks for the kind words... you're welcome

      Delete
  2. Excellent article!!

    I have a question around tunnel interfaces. How is a tunnel interface considered by a router self or the one we assign it to.
    Also if from and to self everything is allowed then can it be assumed that we don't need to put any specific policies for esp, UDP etc. if site to site IPSEC tunnel is required?

    Thank you

    ReplyDelete