COMMAND

    Firewall-1 (ICMP handling)

SYSTEMS AFFECTED

    I guess most systems

PROBLEM

    Bill  Burns  and  his  team  found  following  during some routine
    testing  of  their   Firewall-1  firewall.    They  uncovered   an
    "inconsistency" in how Firewall-1 handles ICMP traffic.  Note that
    there is no known exploit for this, but may be.

    His team was tasked with the goal of finding a way to securely let
    internal  users  use  VitalSigns'  "NetMedic" product work through
    their main firewall.  NetMedic is a clever application that  helps
    analyze  Internet  /   Web  performance  and   help  to   identify
    bottlenecks along the entire chain; from PC to webserver.  One  of
    the more interesting statistics it provides is the "hop count"  to
    the web server  you're browsing".   It does this  in real-time, so
    it's a very useful diagnostic tool.

    Test Setup:
    -----------
    A test firewall was built and representative machines were  placed
    on either side  of the firewall  to accurately portray  what their
    production environment looks like:

        - Checkpoint Firewall-1 v3.0a running on an Ultra Enterprise 2
          with 128M RAM with Solaris 2.5.1+patches
        - a  default  ruleset  of  "deny  all",  including  all    the
          "checkbox" protocols like RealAudio, VDOlive, ICMP
        - no NAT was used (more on this)

    The  "external"  side  of  the  firewall  was connected to an ISDN
    connection back to the main office  so that the PC could hit  real
    web servers.  The "internal" side of the firewall had only the  PC
    on it.

    A PC with Windows95 running NetMedic 1.2 and Netscape Communicator
    as the web browser (NetMedic will work with Microsoft's IE as well
    as Netscape Communicator)

    Both the Solaris "snoop" utility  as well as a Macintosh  Ethernet
    sniffer (EtherPeek) were used to  analyze traffic on each side  of
    the firewall.  EtherPeek was  also used to generate traffic   from
    the external network (more on this later).

    How Microsoft tracert  works: (example assumes  the target is  > 1
    hop away):

        Source machine sends an ICMP type 8 packet ("ping") with a TTL
        value of 1.  Because  this ping packet will only  live through
        one router, the  first router responds  to the source  machine
        with ICMP  type 11  (TTL exceeded).   The source  machine then
        increments the TTL value by  1 and issues another ICMP  type 8
        packet ("ping").  The second router to the path of the  target
        machine responds with an  ICMP type 11 (TTL  exceeded) packet.
        This continues until the TTL  values are high enough to  reach
        the target machine; in this case the ping packet is replied to
        by the target  machine with an  ICMP type 0  ("ping response")
        packet.

    What Bill and his team found:
    -----------------------------
    Since they  knew that  Microsoft's trace  routing utility  (called
    tracert) used ICMP exclusively to calculate hop counts, they  knew
    to support  NetMedic's hop  counting abilities  the only  protocol
    needed was limited to "ICMP".   Since tracert "looked" a lot  like
    ping, they thought they would  start testing by just letting  ping
    through the firewall.

    Their initial assumption on how to accomplish this goal:

        - Firewall-1's stateful inspection
        - combined with checking the checkbox "allow ICMP"
        - specifying that this ICMP  rule be checked "before the  last
          rule"
        - and that a simple rule could be built into the GUI to  allow
          "ping traffic" (there's an example in the book)

    What they noticed was that stateful inspection did not cover ICMP.
    It was perfectly clear  by reading an example  in the book on  how
    to allow ping through  the firewall, but was  not clear was if  it
    was  "stateful"  or   not.   By  reading   the  INSPECT  code   in
    $FWDIR/lib/base.def  it   became  apparent   that  ICMP   was  not
    statefully inspected.

    This meant that if you  allow echo-request packets to go  out, you
    expect echo-reply packets to come back in.  But if it didn't  send
    echo-request packets  out, the  firewall could  still be  allowing
    echo-reply  packets  back  in.   And  if  you  weren't  very, very
    careful other kinds of ICMP packets  could be allowed in.  (To  be
    fair,  the  "allow  ICMP"  checkbox  does  NOT allow the dangerous
    "ICMP redirect" packets  through -- this  is explicitly stated  in
    the INSPECT code.)

    This hypotheses was tested in  real-life by setting up a  suitable
    ruleset in FW-1 and watch  the PC (internal network) ping  the Mac
    (external network).   When stopped the  PC pinging, you  are STILL
    able to shoot the captured echo-reply packets back to the PC.   In
    fact you  could send  multiplied echo-reply  packets back  through
    the firewall DURING  ping sessions.   And even if  you mangled the
    echo-reply  packets  (i.e.  make  them  the  wrong  sequence or ID
    number or change  the destination address)  you are still  able to
    send  them  through  the  firewall.     (Mind  you, "ping" packets
    didn't pass, just the ping-reply packets.)

    Guess os  that the  fact that  you could  not guarantee which ICMP
    packets would be  passed by your  firewall concerned you  greatly.
    Thus packets  could be  sent by  an outsider  without first  being
    requested from an insider.

SOLUTION

    Bill  team  wrote  their  own  stateful inspection INSPECT code to
    handle ping and  tracert.  Btw,  note that Checkpoint  ensure that
    this does not appear to be a flaw in Firewall-1.

    What their code does:
    ---------------------
    This  version  of  the  INSPECT  code  just handles the ability to
    "ping" statefully.  You've got to walk before you can run.   There
    have been three major improvements in  the code so far so that  it
    can handle  NetMedic and  UNIX traceroute  as well.   But this  is
    simple enough that you'll get the point.

    The  code  watches  for  outgoing  ping  packets  and  records the
    following information:

        - source and destination addresses
        - the ICMP sequence number (useful to figure how which packets
          get dropped)
        - the ICMP  ID number (which  process on the  source machine a
          packet belongs to)
        - allows a  maximum of 5  seconds for this  half-open state to
          exist without an echo-reply

    With the code  in place external  machines will be  unable to send
    unexpected echo-reply packets back through the firewall.   Mangled
    packets  (with  incorrect  ID  or  sequence  numbers) will be also
    dropped.   In  both  cases,  unexpected  echo  reply  packets also
    generated a log entry.

    To be  able to  track ICMP  traffic you  need to  "roll your  own"
    INSPECT code.

    This version  of the  code doesn't  include the  code snippet from
    Checkpoint's web page about defeating the "ping of death" packets.
    It was incorporated in subsequent releases.

    If anyone sees any glaring errors or has any other comments  about
    this, send mail to shadow@netscape.com.  Here's the INSPECT code:

    //
    // Stateful ping/tracert v1.2 William D. Burns (shadow@netscape.com)
    //
    //PROPRIETARY SOURCE CODE OF NETSCAPE COMMUNICATIONS CORPORATION
    //Copyright =A9 199x Netscape Communications Corporation. All Rights
    Reserved.
    //
    // This is where the ICMP sequence number is, used for
    //  tracking session-like information with ping,tracert

    #define icmp_ip_seq [ 24 : 2, b ]
    #define PING_Timeout 5
    ping_table = dynamic {} expires PING_Timeout;

    //
    //  PING handling section
    //
    // Accept ping packets going to Internet
    //        ping replies coming from Internet to initiator
    // Reject ping replies not anticipated

    //
    // What a proper echo-request packet looks like
    //
    #define is_ping_request (                       \
      icmp, icmp_type=ICMP_ECHO                           \
    )
    //
    // What a proper echo-reply packet looks like
    //
    #define is_ping_reply (                         \
      icmp, icmp_type=ICMP_ECHOREPLY                \
    )

    // if it looks like an echo packet,
    //   record the session and allow the packet
    //
    #define ping_request_accept (                   \
      is_ping_request,                              \
      record <src,dst,icmp_ip_id; icmp_ip_seq> in ping_table        \
    )

    // if we get a echo_reply without an echo,
    //   or the reply is not the right sequence number
    //   that is a problem so drop it
    //   (icmp_long is predefined in formats.def)
    //
    // Note: the delete looks like an error, but this
    //   is the way is works.  Checking with the
    //   vendor to see if this syntax is what they
    //   intended.
    //
    #define ping_reply_intercept (                  \
      is_ping_reply,                                \
    accept (                                        \
         ((ping_table [dst,src,icmp_ip_id] = icmp_ip_seq), \
         delete <dst,src,icmp_ip_id> from ping_table)       \
      or (log icmp_long, drop)                      \
      )                                             \
    )

    //This is what you you put in the prologue section
    //  and what gets executed on a match for a ping
    //
    #define ping_code {                             \
            ping_request_accept;                    \
            ping_reply_intercept;                   \
    }