Reported by Mark Dowd from McAfee: 1. Upper Layer Protocol Mismatch A problem exists within the netfilter code that is responsible for matching upper layer protocols with rules that are installed by ip6tables (using the protocol "-p" switch) on the system. When a packet is fragmented, the following code in ip6_packet_match() is executed to evaluate whether the incoming packet is a match or not: ---- /* look for the desired protocol header */ if((ip6info->flags & IP6T_F_PROTO)) { int protohdr; unsigned short _frag_off; protohdr = ipv6_find_hdr(skb, protoff, -1, &_frag_off); if (protohdr < 0) return 0; *fragoff = _frag_off; dprintf("Packet protocol %hi ?= %s%hi.\n", protohdr, ip6info->invflags & IP6T_INV_PROTO ? "!":"", ip6info->proto); if (ip6info->proto == protohdr) { if(ip6info->invflags & IP6T_INV_PROTO) { return 0; } return 1; } /* We need match for the '-p all', too! */ ---- This code uses ipv6_find_header() to encapsulate the following logic for fragmented packets: a. If the fragment is a 0-offset fragment, cycle through extension headers to find the upper layer protocol for this packet. If one is not found, return -1. b. If the fragment is not a 0-offset fragment and the fragment header's nexthdr value is not an extension header, return it as the upper layer protocol. c. If the fragment is not a 0-offset fragment and the fragment header's nexthdr value is an extension header, return -1. Using these rules, it is possible to create a fragmented packet that incorrectly evades a match. To illustrate this, consider a scenario where the following rule is installed: ip6tables -A INPUT -p udp -j DROP This rule should drop all UDP packets. This rule can be evaded by sending the following pair of packets: [ IP Header (nxthdr=fraghdr) ] [ fraghdr (offset=0, nxthdr=destopts) ] [ destopts (nxthdr=routeopts) ] [ IP Header (nxthdr=fraghdr) ] [ fraghdr (offset=X, nxthdr=routeopts) ] [ routeopts (nexthdr=UDP) ] [ UDP Data ] The first fragment will not match the DROP udp rule because it doesn't contain a UDP header, and the second one will not match the rule either because the fragment offset is non-zero and the nexthdr in the fragment extension is not UDP. Kernel Versions tested: 2.6.18 2.6.16 2.6.15 (Others supporting IPv6/IPTables are also suspected to be vulnerable) 2. Extension Header match bypass The attack described in the previous scenario can also be used to evade rules that are attempting to block certain extension headers from appearing in the input packets. This is also due to the way ipv6_find_header() is invoked and the same rules are essentially followed, except that when a non-zero offset fragment is encountered the target match will always fail. Consider the following rule: ip6tables -A INPUT -m rt -j DROP The following packet pair will evade this rule: [ IP Header (nxthdr=fraghdr) ] [ fraghdr (offset=0, nxthdr=destopts) ] [ destopts (nxthdr=destopts) ] [ IP Header (nxthdr=fraghdr) ] [ fraghdr (offset=X, nxthdr=destopts) ] [ destopts (nxthdr=routeopts)] [ routeopts ] ... Because the option appears in a non 0-offset fragment, the rule fails to match this packet and it will be successfully delivered (Same Kernel Versions Tested)
The function ipv6_find_hdr() was first introduced with 2.6.14-rc2 and later updated with 2.6.16-rc1. The code in question before 2.6.14 and so also in the RHEL2.1 kernel looks like this: /* look for the desired protocol header */ if((ip6info->flags & IP6T_F_PROTO)) { u_int8_t currenthdr = ipv6->nexthdr; u_int8_t *hdrptr; hdrptr = (u_int8_t *)(ipv6 + 1); do { if (ip6info->proto == currenthdr) { if(ip6info->invflags & IP6T_INV_PROTO) return 0; return 1; } currenthdr = ip6_nexthdr(currenthdr, hdrptr); } while(currenthdr); if (!(ip6info->invflags & IP6T_INV_PROTO)) return 0; }
The attack talks about circumventing the possibly installed DROP rule and make the function return a non-match. RHEL2.1 doesn't seem to be affected to the fragment rule workaround.