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
Thank you, thank you, thank you!
ReplyDeleteyou 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 :)
Thanks for the kind words... you're welcome
DeleteExcellent article!!
ReplyDeleteI 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