Bug 208391 - CVE-2006-4572 IPv6/IP6Tables Vulnerabilities
CVE-2006-4572 IPv6/IP6Tables Vulnerabilities
Status: CLOSED NOTABUG
Product: Red Hat Enterprise Linux 3
Classification: Red Hat
Component: kernel (Show other bugs)
3.0
All Linux
high Severity high
: ---
: ---
Assigned To: Thomas Graf
Brian Brock
impact=important,source=kernelsec,rep...
: Security
Depends On:
Blocks:
  Show dependency treegraph
 
Reported: 2006-09-28 07:02 EDT by Marcel Holtmann
Modified: 2014-06-18 04:29 EDT (History)
4 users (show)

See Also:
Fixed In Version:
Doc Type: Bug Fix
Doc Text:
Story Points: ---
Clone Of:
Environment:
Last Closed: 2006-10-02 08:50:24 EDT
Type: ---
Regression: ---
Mount Type: ---
Documentation: ---
CRM:
Verified Versions:
Category: ---
oVirt Team: ---
RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: ---


Attachments (Terms of Use)

  None (edit)
Description Marcel Holtmann 2006-09-28 07:02:33 EDT
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)
Comment 1 Marcel Holtmann 2006-09-28 08:52:13 EDT
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
RHEL3 kernel looks like this:

        /* look for the desired protocol header */
        if((ip6info->flags & IP6T_F_PROTO)) {
                u_int8_t currenthdr = ipv6->nexthdr;
                struct ipv6_opt_hdr *hdrptr;
                u_int16_t ptr;          /* Header offset in skb */
                u_int16_t hdrlen;       /* Header */

                ptr = IPV6_HDR_LEN;

                while (ip6t_ext_hdr(currenthdr)) {
                        /* Is there enough space for the next ext header? */
                        if (skb->len - ptr < IPV6_OPTHDR_LEN)
                                return 0;

                        /* NONE or ESP: there isn't protocol part */
                        /* If we want to count these packets in '-p all',
                         * we will change the return 0 to 1*/
                        if ((currenthdr == IPPROTO_NONE) || 
                                (currenthdr == IPPROTO_ESP))
                                return 0;

                        hdrptr = (struct ipv6_opt_hdr *)(skb->data + ptr);

                        /* Size calculation */
                        if (currenthdr == IPPROTO_FRAGMENT) {
                                hdrlen = 8;
                        } else if (currenthdr == IPPROTO_AH)
                                hdrlen = (hdrptr->hdrlen+2)<<2;
                        else
                                hdrlen = ipv6_optlen(hdrptr);

                        currenthdr = hdrptr->nexthdr;
                        ptr += hdrlen;
                        /* ptr is too large */
                        if ( ptr > skb->len ) 
                                return 0;
                }

                /* currenthdr contains the protocol header */

                dprintf("Packet protocol %hi ?= %s%hi.\n",
                                currenthdr, 
                                ip6info->invflags & IP6T_INV_PROTO ? "!":"",
                                ip6info->proto);

                if (ip6info->proto == currenthdr) {
                        if(ip6info->invflags & IP6T_INV_PROTO) {
                                return 0;
                        }
                        return 1;
                }

                /* We need match for the '-p all', too! */
                if ((ip6info->proto != 0) &&
                        !(ip6info->invflags & IP6T_INV_PROTO))
                        return 0;
        }
Comment 2 Marcel Holtmann 2006-10-02 08:50:24 EDT
The attack talks about circumventing the possibly installed DROP rule and make
the function return a non-match. RHEL3 doesn't seem to be affected to the
fragment rule workaround.

Note You need to log in before you can comment on or make changes to this bug.