IPFW:check-state/keep-state advanced stateful rules. By Joe Barbish 02/20/2002 All rights reserved. As most new ipfw users, I had a typical ipfw rules file built from the simple stateful type in rc.firewall. I had originally been using user ppp with it's internal Nat function, but went to natd as the simple stateful type in rc.firewall showed. Since the sample rc.firewall (simple) was pretty much just what I wanted to do, I just assumed this was the correct and proper way, so I cut out the simple type code from rc.firewall to create my own ipfw firewall rules. In searching FBSD and the many sites found by google search I saw many many other people before me had done the same thing. From a technical point of view the whole rc.firewall file is based on simple stateful rules using setup/established with some stateless rules thrown in. As a new ipfw user I did not know the difference and the comments sure did not call out the difference. When I tried to change my simple stateful [established/setup] to advanced stateful [check-state/keep-state] rules, I kept having trouble with ip address being mismatched. Technically the mismatches showed up in /var/log/security as packets that got denied by the default deny everything that reaches the end with out matching any rule. I spend weeks playing around trying different combination of ipfw rules, but kept having mismatches in the dynamic table. Finally I removed the natd divert rule from the ipfw rules set and deactivated natd in rc.conf and re-activated ppp -Nat in rc.conf, and the advanced stateful [check-state/keep-state] rules started to work. The real problem here is natd and ipfw advanced stateful rules were not designed to work together. IPFW was designed as a firewall where the ip addressed of all the machines behind the firewall use public ip addresses assigned from your isp. In this configuration ipfw has no problems making matches in the dynamic table for packet flow. When the natd divert rule is added to the ipfw rule set now you are converting ip address (from public to private on packets coming in from the internet and converting private ip address to public for packets leaving the lan for the internet) right in the middle of checking the dynamic table for matches by ip address, flow direction, and packet sequence number. This ip address translation in the middle of ipfw creates mismatches in the dynamic table. Many many users reach this point using the advanced check-state/keep-state stateful rules and go back to simple stateful rule set using established/setup simple because they can not get the advanced stateful rules to work. All because they are under the impression that natd has to be in the ipfw rule set. To understand why, we have to look at the development history of IPFW. IPFW first showed up in FBSD 2.0, containing the stateless rule set and the simple stateful [established/setup] rule set, the dummynet function was introduced in FBSD 2.2.8 and the advanced check-state/keep-state stateful rules in FBSD 4.0. The rc.firewall file was created for FBSD 2.0 and has not been updated to fully utilize the advanced stateful rule set, so it is a very poor example to be using for your first ipfw rules set. The lesson here is to never, never accept anything in FBSD documentation as the real truth, question everything. The real solution to advanced stateful mis-matches is to remove natd from the ipfw rules set and use user ppp -nat or standalone natd to do the ip address translation before the packets get handed off to ipfw. This way ipfw all ways sees the same ip address that created the dynamic table rule entry. I used user ppp -nat because I was all ready using user ppp and did not want to start another permanent running task. Other IP stack security options. The main run control configuration file /etc/rc.conf has a whole group of run time security options to control the flood of falsified packets entering the system which get control before IPFW evens knows their coming in. The following is from my rc.conf file. # Activate user ppp auto start at boot time ppp_enable="YES" # Start User ppp task ppp_mode="ddial" # ddial, auto, background ppp_profile="dialisp" # section in ppp.conf to exec ppp_nat="YES" # turn on user ppp nat feature # Required IPFW kernel firewall support # For more info see # www.onlamp.com/pub/a/bsd/2001/04/25/FreeBSD_Basics.html # firewall_enable="YES" # Start daemon firewall_script="/etc/ipfw.stdrules" # run my custom rules if present # sh /etc/ipfw.stdrules will load # new rules file after editing. filewall_logging="YES" # Enable events logging # Extra firewalling options log_in_vain="YES" # NO is default. YES enables logging of # connection attempts to ports that have no # listening socket on them. Put msg on consol icmp_drop_redirect="YES" # YES will cause the kernel to ignore # ICMP REDIRECT packets. tcp_drop_synfin="YES" # YES will cause the kernel to ignore TCP # frames that have both the SYN and FIN flags # set. Only available if the kernel was built # with the TCP_DROP_SYNFIN option. # change to NO if web server behind firewall. tcp_restrict_rst="YES" # YES will cause the kernel to refrain from # emitting TCP RST frames in response to # invalid TCP packets (e.g., frames destined # for closed ports). This option is only # available if the kernel was built with the # TCP_RESTRICT_RST option. syslogd_flags="-ss" # Don't use network sockets so portscan # will not find (security tip) portmap_enable="NO" # Don't allow nfs portmapper (security tip) The log_in_vain="YES" option will post a message to the root console screen every time it stops a packet. This became very annoying so I changed the syslog to put these messages in the security log. All the ipfw messages that were going to the /var/log/security file was also going to the /var/log/message file. I did not think it was wise to be posting ipfw messages in more that one place, so I stopped them from going to the message file. Below are the lines I changed in /etc/syslog.conf to make this happen. The original lines. *.err;kern.debug;auth.notice;mail.crit /dev/console *.notice;kern.debug;lpr.info;mail.crit;news.err /var/log/messages security.* /var/log/security replaced by this lines # kern.info is where the log_in_vain messages come from. The following # will stop the log_in_vain messages from coming out on root console & # put them in the security log. 2/20/2002 Joe Barbish # remove kern.info messages from /dev/console & /var/log/messages # and put them into /var/log/security. *.err;auth.notice;mail.crit /dev/console kern.notice;kern.=debug /dev/console *.notice;lpr.info;mail.crit;news.err /var/log/messages kern.notice;kern.=debug /var/log/messages security.*;kern.=info /var/log/security Another very obscure option is blackhole, new in FBSD 4.4 The blackhole sysctl(8) is used to control system behavior when connection requests are received on TCP or UDP ports where there is no socket listening. Normal behavior, when a TCP SYN segment is received on a port where there is no socket accepting connections, is for the system to return a RST segment, and drop the connection. The connecting system will see this as a "Connection reset by peer". By setting the TCP blackhole MIB to a numeric value of 1, the incoming SYN segment is merely dropped, and no RST is sent, making the system appear as a blackhole. By setting the MIB value to 2, any segment arriving on a closed port is dropped without returning a RST. This provides some degree of protection against stealth port scans. In the UDP instance, enabling blackhole behavior turns off the sending of an ICMP port unreachable message in response to a UDP datagram which arrives on a port where there is no socket listening. It must be noted that this behavior will prevent remote systems from running traceroute(8) to a system. The blackhole behavior is useful to slow down anyone who is port scanning a system, attempting to detect vulnerable services on a system. It could potentially also slow down someone who is attempting a denial of service attack. The sysctl net.inet.tcp.blackhole=2 command can be entered from the command line and will be in effect until the next boot. The sysctl command can also be in the /etc/sysctl.conf file (which you must create) and if present will be activated during the boot process. Read man sysctl for command format to display settings of this option and some others that allow you to change to default dynamic rules time out values. For the really advanced technical ipfw user check out ipfw user patches at http://people.freebsd.org/~cjc/ See http://bsdvault.net/sections.php?op=viewarticle&artid=57 for info on sysctl. See http://www.practicallynetworked.com/sharing/app_port_list.htm for a list of ports used by different applications. /etc/sysctl.conf file contents sysctl net.inet.tcp.blackhole=2 sysctl net.inet.udp.blackhole=1 Here are the statements for the kernel source to include IPFW in the kernel. # # The following options add sysctl variables for controlling how certain # TCP packets are handled by the kernel. # options ICMP_BANDLIM # Enables icmp error response bandwidth # limiting. This will help protect from # D.O.S. packet attacks. option TCP_DROP_SYNFIN # Adds support for ignoring TCP packets # with SYN+FIN. This prevents nmap from # identifying the TCP/IP stack, but # breaks support for RFC1644 extensions # & is not recommended for web servers. # not supported in 4.4 #option TCP_RESTRICT_RST # Adds support for blocking emission of # TCP RST packets. Useful in limiting # SYN floods & port scaning. # Enable kernel IPFW, the FBSD suppiled packet filtering and accounting system # Has a FBSD suppiled user land control utility ipfw. # option IPFIREWALL # Adds filtering code into kernel option IPFIREWALL_VERBOSE # enable logging thru syslogd(8) option IPFIREWALL_VERBOSE_LIMIT=10 # stop attack via syslog flooding option IPDIVERT # needed to use natd from IPFW option IPSTEALTH # Enables code to support stealth # forwarding of packets without # touching the ttl. Useful to hide # firewalls from traceroute and # similar tools. Here is my advanced stateful ipfw rule set for you to cut and past from. ########################################################################### # # Define IPFW firewall rules for gateway.xxxxxxx.com # 2/15/2002 Joe Barbish # # User ppp tun0 dial out to ISP with dynamic IP addresses assigned. # User ppp tun1 dial in to this box with dynamic IP addresses assigned # User ppp tun2 dial in to this box with dynamic IP addresses assigned # User ppp nat used. Private Ip address used inside. # 3 win98 boxes on LAN with static IP address hard coded. # Protect the whole private network from loss of service attacks # These rules can be reloaded with out rebooting by issuing this command # sh /etc/ipfw.stdrules # # The use of 'me' in rules means IP address 127.0.0.0 localhost # # Firewall Policy Statement. # All packet traffic originating behind this firewall not requiring access # to the public internet is exempt from these firewall rules. # # Each public internet function must be explicitly allowed by a rule. # Only valid response to the packets I've sent out are allowed in. # All packets must use the IPFW advanced "dynamic" rules function. # No state-less rules or simple-stateful rules are allowed. # ############################################################################ # # Set rules command prefix # The -q option on the command is for quite mode. # Do not display rules as they load. Remove during development to see. fwcmd="/sbin/ipfw -q" # Flush out the list before we begin. $fwcmd -f flush # Set defaults # set these to your outside interface network and netmask and ip # for dynamic IP address from ISP use there range oif="tun0" odns1="218.216.115.11" # ISP's dns server 1 IP address odns2="218.216.115.12" # ISP's dns server 2 IP address oisp="218.216.115.4" # ISP router issueing rip oip="163.170.155.25/24" # For testing dial isp from standalone pc and # access this FBSD box over the internet. # This value is the dynamic IP address range # issued by ISP. oip is in inbound section # statements to only allow inbound access from me. # /24 means 63.70.155.1 thru 63.70.155.256 # Set these to your inside interface network and ip address range iif="xl0" # Nic card iip="10.0.10.2/29" # Private IP address range on Nic card # /29 means 10.0.10.1 thru 10.0.10.08 # 10.0.10.2 Lan Nic card # 10.0.10.5 Lan Windows98 machine1 iip2="10.0.0.1/29" # Private IP address range for dial in # /29 means 10.0.0.1 thru 10.0.10.08 # 10.0.0.2 User PPP Dialin Host # 10.0.0.5 User PPP Dialin Windows98 machine1 # This is the start of the rules. # All traffic coming in from the internet or # leaving the local LAN start here # Handle router 520 rip request $fwcmd add 00002 deny udp from $oisp 520 to me in via $oif #*** TESTING PURPOSES ONLY *** TESTING PURPOSES ONLY *** TESTING PURPOSES ONLY # The following rule if un-commented will change the behavior of this # FireWall rule set from closed to completely open, thus bypassing all of the # following rules. This single rule is placed here for TESTING PURPOSES ONLY. #$fwcmd add 00005 allow all from any to any # Internal gateway housekeeping # Rules # 100 - 130 exempt everything behind the firewall from this rules set. # Rules # 150 & 160 deny the reference to the localhost default IP address. $fwcmd add 00100 allow ip from any to any via lo0 # allow all localhost $fwcmd add 00110 allow ip from any to any via xl0 # allow all local LAN $fwcmd add 00120 allow ip from any to any via tun1 # allow all dialin call 1 $fwcmd add 00130 allow ip from any to any via tun2 # allow all dialin call 2 $fwcmd add 00150 deny ip from any to 127.0.0.0/8 # deny use of localhost IP $fwcmd add 00160 deny ip from 127.0.0.0/8 to any # deny use of localhost IP ######## control section ############################################ # Start of IPFW advanced Stateful Filtering using "dynamic" rules. # The check-state statement behavior is to match bi-directional packet traffic # flow between source and destination using protocol/IP/port/sequence number. # The dynamic rule has a limited lifetime which is controlled by a set of # sysctl(8) variables. The lifetime is refreshed every time a matching # packet is found in the dynamic table. # Allow the packet through if it has previous been added to the # the "dynamic" rules table by an allow keep-state statement. $fwcmd add 00500 check-state # Deny any late arriving packets so they don't # get caught & logged by rules 800 or 900. $fwcmd add 00502 deny all from any to any frag # Deny ACK packets that did not match the dynamic rule table $fwcmd add 00501 deny tcp from any to any established ######## outbound section ############################################ # Interrogate packets originating from behind the firewall, private net. # Upon a rule match, it's keep-state option will create a dynamic rule. # Allow out www function $fwcmd add 00600 allow tcp from any to any 80 out via $oif setup keep-state # Allow lan winbox access to FBSD Apache13/Frontpage Server $fwcmd add 00601 allow tcp from $iip to any 80 out via $oif setup keep-state # Allow out access to my ISP's Domain name server. $fwcmd add 00610 allow tcp from any to $odns1 53 out via $oif setup keep-state $fwcmd add 00611 allow udp from any to $odns1 53 out via $oif keep-state $fwcmd add 00615 allow tcp from any to $odns2 53 out via $oif setup keep-state $fwcmd add 00616 allow udp from any to $odns2 53 out via $oif keep-state # Allow out access to internet Domain name server. $fwcmd add 00618 allow tcp from any to any 53 out via $oif setup keep-state $fwcmd add 00619 allow udp from any to any 53 out via $oif keep-state # Allow out send & get email function $fwcmd add 00630 allow tcp from any to any 25,110 out via $oif setup keep-state # Allow out & in FBSD (make install & CVSUP) functions # Basically give user id root "GOD" privileges. $fwcmd add 00640 allow tcp from me to any out via $oif setup keep-state uid root $fwcmd add 00641 allow tcp from any to me in via $oif setup keep-state uid root # Allow out ping $fwcmd add 00650 allow icmp from any to any out via $oif keep-state # Allow out FTP control channel $fwcmd add 00671 allow tcp from any to any 21 out via $oif setup keep-state # Allow in FTP data channel to Lan ip range $fwcmd add 00672 allow tcp from any 20 to $iip 1024-49151 in via $oif setup keep-state # Allow in FTP data channel to Dialin users ip range $fwcmd add 00673 allow tcp from any 20 to $iip2 1024-49151 in via $oif setup kee # Allow out ssh $fwcmd add 00680 allow tcp from any to any 22 out via $oif setup keep-state # Allow out TELNET $fwcmd add 00690 allow tcp from any to any 23 out via $oif setup keep-state # Allow out Network Time Protocol (NTP) queries $fwcmd add 00694 allow tcp from any to any 123 out via $oif setup keep-state $fwcmd add 00695 allow udp from any to any 123 out via $oif keep-state # Allow out Time $fwcmd add 00696 allow tcp from any to any 37 out via $oif setup keep-state $fwcmd add 00697 allow udp from any to any 37 out via $oif keep-state # Allow out ident $fwcmd add 00700 allow tcp from any to any 113 out via $oif setup keep-state $fwcmd add 00701 allow udp from any to any 113 out via $oif keep-state # Allow out IRC $fwcmd add 00710 allow tcp from any to any 194 out via $oif setup keep-state $fwcmd add 00711 allow udp from any to any 194 out via $oif keep-state # Allow out whois $fwcmd add 00712 allow tcp from any to any 43 out via $oif setup keep-state $fwcmd add 00713 allow udp from any to any 43 out via $oif keep-state # Allow out whois++ $fwcmd add 00715 allow tcp from any to any 63 out via $oif setup keep-state $fwcmd add 00716 allow udp from any to any 63 out via $oif keep-state # Allow out finger $fwcmd add 00720 allow tcp from any to any 79 out via $oif setup keep-state $fwcmd add 00721 allow udp from any to any 79 out via $oif keep-state # Allow out nntp news $fwcmd add 00725 allow tcp from any to any 119 out via $oif setup keep-state $fwcmd add 00726 allow udp from any to any 119 out via $oif keep-state # Allow out gopher $fwcmd add 00730 allow tcp from any to any 70 out via $oif setup keep-state $fwcmd add 00731 allow udp from any to any 70 out via $oif keep-state ######## inbound section ############################################ # Interrogate packets originating from in front of the firewall, public net. # Place statements here to allow public requests for service. # The ${oip} holds the dynamic ip address range that both this FBSD box and # the standalone pc I use for testing logs into, so the result is only I can # gain public access from the internet to these functions. # Allow in www $fwcmd add 00800 allow tcp from $oip to any 80 in via $oif setup keep-state # Allow TCP FTP control channel in & data channel out $fwcmd add 00810 allow tcp from $oip to me 21 in via $oif setup keep-state $fwcmd add 00811 allow tcp from $oip 20 to any 1024-49151 out via $oif setup keep # Allow in ssh function $fwcmd add 00820 allow log tcp from $oip to me 22 in via $oif setup keep-state # Allow in Telnet $fwcmd add 00830 allow tcp from $oip to me 23 in via $oif setup keep-state ######## catch all section ############################################ # This sends a RESET to all ident packets. $fwcmd add 00840 reset tcp from any to me 113 in via $oif # Stop & log spoofing Attack attempts. # Examine incoming traffic for packets with both a source and destination # IP address in my local domain as per CIAC prevention alert. $fwcmd add 00850 deny log ip from me to me in via $oif # Stop & log ping echo attacks # stop echo reply (ICMP type 0), and echo request (type 8). $fwcmd add 00860 deny log icmp from any to me icmptype 0,8 in via $oif # Reject & Log all setup of incoming connections from the outside $fwcmd add 00900 deny log all from any to any in via $oif # Everything else is denied by default # deny and log all packets that fell through to see what they are $fwcmd add 00910 deny log logamount 500 ip from any to any