ipfw Rules, 2007/11/16 revision
Thanks to the feedback, I’ve tweaked these rules a bit. I realized I was blocking DHCP, so I added that as well.
Chris
# DO NOT USE THESE RULES without customizing them first!
# Version: 2007/11/16
# For more information, see http://securosis.com/2007/11/15/ipfw-rules/
# These rules *MUST* be customized to your requirements.
# In particular, if you have a private home network (behind an AirPort
# Base Station, Linksys WRT54G, etc.), change “10.42.24.0/24″ below to
# your private network range.
# Additionally, allow only ports you actually use; other ports should be
# blocked by the ipfw firewall.
# Thanks to:
# Rich Mogull http://securosis.com
# windexh8er: http://www.slash32.com/
# Lee: http://thnetos.wordpress.com/
# Chris Pepper http://www.extrapepperoni.com/
# Apple (Server Admin is a good way to create an ipfw ruleset)
# http://www.apple.com/server/macosx/
# FreeBSD (where Apple got ipfw) http://www.freebsd.org/
# We don’t really want this, but it’s unavoidable on Mac OS X Server, so
# document it here (serialnumberd).
# 100 allow udp from any 626 to any dst-port 626
# Let me talk to myself over the loopback.
add 200 allow ip from any to any via lo0
# Loopback addresses on non-loopback interfaces are bogus.
add 300 deny log logamount 1000 ip from any to 127.0.0.0/8
add 310 deny log logamount 1000 ip from 224.0.0.0/4 to any in
# Block multicast if you don’t use it.
# add 400 deny log ip from 224.0.0.0/4 to any in
# Accept responses to my client programs.
add 500 check-state
# If we let the conversation begin, let it continue.
add 600 allow tcp from any to any established
# Let my programs go!
add 610 allow tcp from any to any out keep-state
add 620 allow udp from any to any out keep-state
# Block bogus inbounds that claim they were established.
# add 630 deny log tcp from any to any established in
# Change this to DENY fragments if you don’t need them.
add 700 allow udp from any to any in frag
# Allow DHCP responses (DHCP broadcasts, which seems to defeat
# keep-state).
add 800 allow udp from any to any src-port 67 dst-port 68 in
# add 1000 allow icmp from 10.9.7.0/24 to any icmptypes 8
# Server Admin provides these by default.
add 1100 allow icmp from any to any icmptypes 0
add 1110 allow igmp from any to any
# mDNS (Bonjour) from trusted local networks (fill in your own,
# preferably non-standard, networks after ‘from’).
# For Back to My Mac, you might need this from ‘any’.
# add 5000 allow udp from 10.42.24.0/24 to any dst-port 5353
# add 5010 allow udp from 10.42.24.0/24 5353 to any dst-port 1024-65535 in
# DNS (note TCP is required, but this one should scare you — much
# better to only allow packets from your trusted nameservers, if you
# always use the same ones).
add 5100 allow tcp from any to any dst-port 53
add 5110 allow udp from any to any dst-port 53
add 5120 allow tcp from any to any dst-port 53 out keep-state
add 5130 allow udp from any to any dst-port 53 out keep-state
# ssh
add 5200 allow tcp from any to any dst-port 22
# iTunes music sharing
# add 5300 allow tcp from 10.42.24.0/24 to any dst-port 3689
# AFP
# add 5400 allow tcp from 10.42.24.0/24 to any dst-port 548
# HTTP (Apache); HTTPS
# add 5500 allow tcp from any to any dst-port 80
# add 5510 allow tcp from any to any dst-port 443
# L2TP VPN
# add 5600 allow udp from any to any dst-port 1701
# add 5610 allow esp from any to any
# add 5620 allow udp from any to any dst-port 500
# add 5630 allow udp from any to any dst-port 4500
# iChat: local
# add 5700 allow tcp from 10.42.24.0/24 to any dst-port 5298
# add 5710 allow udp from 10.42.24.0/24 to any dst-port 5298
# add 5720 allow udp from 10.42.24.0/24 to any dst-port 5297,5678
# Server Admin SSL (Mac OS X Server only)
# add 5800 allow tcp from 10.42.24.0/24 to any dst-port 311
# add 5810 allow tcp from 10.42.24.0/24 to any dst-port 427
# add 5820 allow udp from 10.42.24.0/24 to any dst-port 427
# syslog
# add 5900 allow udp from 10.42.24.0/24 to any dst-port 514
# ipp (CUPS printing)
# add 6000 allow tcp from 10.42.24.0/24 to any dst-port 631
# MTU discovery
add 10000 allow icmp from any to any icmptypes 3
# Source quench
add 10100 allow icmp from any to any icmptypes 4
# Ping out; accept ping answers.
add 10200 allow icmp from any to any icmptypes 8 out
add 10210 allow icmp from any to any icmptypes 0 in
# Allow me to traceroute.
add 10300 allow icmp from any to any icmptypes 11 in
# My default policy: log and drop anything that hasn’t matched an allow
# rule above
add 65534 deny log logamount 1000 ip from any to any
# Hard-coded default allow rule (compiled into Darwin kernel)
add 65535 allow ip from any to any








rob Nov 16
One more suggestion: drop 310 and uncomment 400, as they’re essentially the same.
I’m still not clear about the 600 range; again, doesn’t keep-state make 600 unnecessary, and 630 desirable? And where is windexh8er anyway? 630 was his suggestion in the first place, he should come explain it. :)
rob Nov 16
You know, I posted that and then went to get a drink and found myself thinking, “WTF are you asking them for? Just do it and see what happens.” So, nevermind answering that.
Pepper Nov 17
Rob,
You’re right on both.
windexh8er Nov 17
Yeah, the DHCP gets broke in keep-state because if we send to a broadcast address we expect the response from the address that it was sent to. DHCP doesn’t work like that which is why you need to allow it back in.
Rob,
Yeah, the checkstate in 500 trumps the established in 600. I don’t know why that was added — it doesn’t make any sense.
630 works because of TCP flags. Let me explain. 500 should catch anything that’s already established, so if it fails on 500 and gets to 630 that means that it is an ACK from someone who we never sent a SYN to. Hopefully that clears it up a bit, it’s very popular in IPFW firewalls to immediately follow the check-state with the deny frags and deny established.
rob Nov 17
windexh8er,
thanks for the clarification. I’ve got another question, if y’all don’t mind. That section of my rules now looks like this:
00300 check-state
00340 allow tcp from any to any out keep-state
00350 allow udp from any to any out keep-state
00400 deny log tcp from any to any established in
00410 deny log ip from any to any frag
and there are a lot of rejects in the log that look like this (munged):
Nov 17 13:05:03 Firewall[48]: 400 Deny TCP [source ip]:80 [my ip]:50224 in
These seem to be connections from websites I have visited, but the dynamic rule is being dropped before the website is done sending data, I guess. The pages load ok, so I can’t think of any other reason for it, and it doesn’t happen if I have “allow established” rule in. Comments appreciated.
Pepper Nov 17
windexh8r,
You and I agree on the idea, but the reality is not so:
# If we let the conversation begin, let it continue.
add 600 allow tcp from any to any established
# Let my programs go!
add 610 allow tcp from any to any out keep-state
add 620 allow udp from any to any out keep-state
# Block bogus inbounds that claim they were established.
# add 630 deny log tcp from any to any established in
The packets 630 matches are a proper subset of the packets 600 matches, so Rob’s right — 630 is pointless.
As for 500 (check-state), I tend to see it early in the ruleset, presumably because most data transferred to/from normal users is surfing, so getting HTTP/HTTPS pages back ASAP is a performance win. 600 is already gone from my draft for the next revision of these rules.
Rob,
Have you tried “sudo ipfw -de list” to list live & expired dynamic rules? Presumably the timeout is too short for your web surfing, which is odd. Perhaps these are funky ad or user tracking bits you really do want to drop?? You might want to sniff, with “sudo tcpdump -s0 -xX -i en0 port 80 and host [source ip]” (assuming Ethernet rather than en1, AirPort), to see what’s coming back late from those web sites; “tail -f” against the log file is good for noticing when the rules trip.
rob Nov 18
Chris,
well, this is curious, at least to me. It looks like the server is sending responses to a port other than the one that initially opened the connection. But then, the browser opens a connection on that port shortly thereafter. So the fw log looks like this:
Nov 17 20:00:52 Firewall[48]: 400 Deny TCP them:80 me:49807 in
But then three seconds later tcpdump shows this:
20:00:55.130098 IP me.49807 > them.http: F 3365427958:3365427958(0) ack 3067830661 win 1024
Is the server expecting to be able to send stuff back on different ports? In fact, further down I see packets being accepted on port 49806, which isn’t in the list of of expired rules.
Guess I need to learn more about how http works.
Thanks, BTW, for taking the time to look at this.
windexh8er Nov 18
Pepper, actually 630 is not pointless. As if I generate and ACK that is not established back in then I willl get a log generated. 600 is the useless rule, and although you’ll never see “hits” on the check-state rule the rule actually applies the hit to the rule that the state was built from. Trust me, I’ve done BSD firewalls since 1998. This is how IPFW works and if you look through the FreeBSD IPFW manual you’ll see the exact same thing explained — Dru Lavigne is another good reference to FreeBSD and it’s components. In the end I would highly recommend removing 600 and enable 630 as it’s not a good practice with IPFW since 500 is doing 600’s job.
–windexh8er
windexh8er Nov 18
As an example, these are rules that hit my “630″ (mine is rule 502 however) rule just over the past couple of days…
Nov 17 10:26:40 blackbox Firewall[39]: 502 Deny TCP 170.135.216.161:443 10.100.0.121:49412 in via en1
Nov 17 10:26:47 blackbox Firewall[39]: 502 Deny TCP 170.135.216.161:443 10.100.0.121:49414 in via en1
Nov 17 10:26:47 blackbox Firewall[39]: 502 Deny TCP 170.135.216.209:443 10.100.0.121:49420 in via en1
Nov 17 10:27:01 blackbox Firewall[39]: 502 Deny TCP 170.135.216.209:443 10.100.0.121:49422 in via en1
Nov 18 11:07:33 blackbox Firewall[39]: 502 Deny TCP 65.114.90.196:80 10.100.0.121:49970 in via en1
Nov 18 11:07:41 blackbox Firewall[39]: 502 Deny TCP 65.114.90.196:80 10.100.0.121:49970 in via en1
Nov 18 11:08:05 blackbox Firewall[39]: 502 Deny TCP 65.114.90.196:80 10.100.0.121:49970 in via en1
Nov 18 11:08:36 blackbox Firewall[39]: 502 Deny TCP 65.114.90.196:80 10.100.0.121:49970 in via en1
Nov 18 11:08:51 blackbox Firewall[39]: 502 Deny TCP 65.114.90.196:80 10.100.0.121:49970 in via en1
Again, here are my first 6 rules:
00100 allow ip from any to any via lo*
00110 deny ip from 127.0.0.0/8 to any in
00120 deny ip from any to 127.0.0.0/8 in
00500 check-state
00501 deny log ip from any to any frag
00502 deny log tcp from any to any established in
…and that is pretty much standard best practice.
–windexh8er
windexh8er Nov 18
Rob, to answer your question of how TCP socket communication works with regards to HTTP traffic…
Your computer will request a connection to, say 1.1.1.1 on port 80/TCP. Your computer has to request this from a port, which is assigned dynamically per connection. It has to be something greater than 1024 (especially on your Mac — because the OS doesn’t allow any programs not running as root to bind to less than or equal to 1024). So a request will get generated like this:
your.computer:45000 –> 1.1.1.1:80
At that point the web server knows where to send the response back. The firewall’s job is to track these connections so that it knows to let a connection from 1.1.1.1 back into your local machine on 45000. That is a dynamic rule entry that will eventually be purged…
So in the end the entire round trip (minus hand shake) can just look like two entries (for a simple request):
your.computer:45000 –> 1.1.1.1:80
1.1.1.1:80 –> your.computer:45000
Prior to that is where the SYN, SYN/ACK, ACK comes into play… And why, yes, 630 is a valuable rule. :) I recommend reading a little bit of TCP connection establishment material for those who may be confused or misunderstanding:
http://en.wikipedia.org/wiki/Transmission_Control_Protocol#Connection_establishment
–windexh8er
rob Nov 18
windexh8er,
I understand the part that you’ve described, what I don’t understand is the server sending responses to other ports. i.e., the connection I was looking at was opened by me on port 49810, but then there is a connection attempt from the server to port 49807 which shows up as a deny in the log, then later still there is a connection to 49806, which is accepted even though it doesn’t show up in the list of dynamic rules. I’d expect the whole exchange to happen over one port, not multiple ports.
A quick scan of the page you linked to doesn’t look like it addresses this. Thorough reading will have to wait, I promised my wife I’d do some stuff around the house today. :)
Thanks.
rob Nov 18
Oh yeah, and ISTM that you guys are basically in agreement with each other. If you have allow established (600 in Chris’ example), then ipfw will stop parsing and passing the packet, it’ll never get to deny established (630). So deny established is useful if you are doing check-state/keep state. But then, maybe I’m missing some subtlety.
Now back to work…
rob Nov 18
s/passing/pass
windexh8er Nov 18
Rob,
Not exactly. What I’m saying is that with 610 and 620 in there, which are correct the line number 600 makes no sense. I don’t want a rule that says from ‘any to any’ and have it not directed as in or out. Because line 600 is abov line 630 that renders 630 useless. Because 600 says that I’ll let any ACK in — that is just bad practice. Line 500 works because if I removed line 600 from the example and run, say, some web traffic then 610 will catch it outbound. When the response comes back line 500 will have the dynamic entry caught and pair it to line 610 which is accepted and the traffic will come back in. 630 will block any of those ACKs in that we never SYN’d. This is my whole point of why 600 is useless (because it in fact BREAKS 630) and it is just bad security practice.
Josh Dec 8
Disclaimer: I’ve never used OS X, but I use and run many FreeBSD workstations and servers. I’m not a networking, security or IPFW guru, but IMHO lines 5100-5130 (and many others) can be replaced. I would remove TCP for DNS entirely. I’ve only seen it used rarely on DNS servers, but maybe OS X is different.
I agree that 600 is useless with 610 in there, BUT 620 is not involved! 600 only matches established TCP, not UDP. I do think having both 610 and 620 makes 500 useless, though I suppose it’s a “shortcut” which might save a tiny bit of performance. Personally this is what I do, which may or may not be better:
add allow tcp from me to any out via ext0 setup keep-state
add allow udp from me to any out via ext0 keep-state
That’s it. Other than performance, is there any reason to use the “check-state…allow” method?
Lastly, with lines 610 and 620 in there, much of the rest of the ruleset seems redundant to me. If you want to allow everything outbound, every line after that should be inbound, with a few exceptions (like ICMP), e.g.:
add allow tcp from any to me http in via ext0 setup keep-state
Of course, I’m not really sure what sort of policy you’re trying to enforce. I also never thought about the frag line so I dunno if my method prevents that. I do think there is a large lack of “direction” in the rules after 620 (in/out, me to any, any to me, etc.). Food for thought though, hope it helps. :)
Pepper Dec 11
All,
Thanks for your input; based on the feedback, I just posted a revised version; Rich will also provide a permanent URL, which will stay current if we do a 4th round.
Chris
mmd Dec 12
Get the revised rule set: https://securosis.com/wp-content/uploads/2007/11/ipfw-securosis.txt.
Your URL is slightly in error, “http” not “https”
I guessed after waiting a long time for the page to load.