COMMAND

    ssh

SYSTEMS AFFECTED

    ssh-1.X

PROBLEM

    This was submitted  to the Freebsd  bug tracking system,  although
    there are doubtless other vendors who leave this package,  despite
    the  existence  of  the  ssh-2.X.     While  Debian  appears to be
    immune, Frank  (the original  poster) was  able to  crash his  ssh
    daemon (much to  his dismay), and  there appears the  potential to
    execute arbitrary code, as long  as you encrypt it first...   Here
    is the freebsd report.. it describes the method to crash a  remote
    Ssh daemon (lets hope you ran sshd from your xinetd, etc).

        http://www.freebsd.org/cgi/query-pr.cgi?pr=14749

    ssh 1.x  is usually  used because  neither 1.x  or 2.x  are really
    free   software,   but   1.x's   license   at   least  allows  for
    non-commercial  use  in  a  commercial  setting.   The 2.x license
    states that any use in a commercial setting requires purchasing  a
    $500 license.   Hence the  lsh and  OpenSSH projects.   This  will
    probably  keep   2.x  from   ever  really   catching  on   outside
    universities.

    Jochen  Bauer  took  a  closer  look  at  the problem.  Here's his
    analysis.   In  sshd.c,  around  line  1513  the  client-generated
    session key,  that has  been encrypted  with the  server and  host
    public keys, is received from  the client as a multiple  precision
    integer.

        /* Get the encrypted integer. */
          mpz_init(&session_key_int);
          packet_get_mp_int(&session_key_int);

    The encrypted  session key  is then  (around line  1525) passed to
    rsa_private_decrypt to do the first part of the decryption,  which
    is either decryption  using the server  private key or  decryption
    using the host private key, depending on which key has the  larger
    modulus.

        rsa_private_decrypt(&session_key_int, &session_key_int,
                                  &sensitive_data.private_key);

    If  RSAREF  is  used  (i.e.  RSAREF  is  defined in the code), the
    rsa_private_decrypt function in rsaglue.c (around line 162)  looks
    like:

        void rsa_private_decrypt(MP_INT *output, MP_INT *input, RSAPrivateKey *key)
        {
          unsigned char input_data[MAX_RSA_MODULUS_LEN];
          unsigned char output_data[MAX_RSA_MODULUS_LEN]
          unsigned int input_len, output_len, input_bits;
          [...]
          input_bits = mpz_sizeinbase(input, 2);
          input_len = (input_bits + 7) / 8;
          gmp_to_rsaref(input_data, input_len, input);
          [...]
        }

    The    trouble    spot    is     the    fixed    length     buffer
    input_data[MAX_RSA_MODULUS_LEN].   A  pointer  to  this  buffer is
    passed  to  the  conversion  function  gmp_to_rsaref  along with a
    pointer to the encrypted session key and the length (input_len) of
    the   encrypted   session   key,   which   may   be  greater  than
    [MAX_RSA_MODULUS_LEN].  gmp_to_rsaref  (located around line  79 of
    rsaglue.c) simply calls mp_linearize_msb_first(buf, len, value).

        void gmp_to_rsaref(unsigned char *buf, unsigned int len, MP_INT *value)
        {
          mp_linearize_msb_first(buf, len, value);
        }

    mp_linearize_msb_first  is  contained  in  mpaux.c around line 41.
    The function looks like:

        void mp_linearize_msb_first(unsigned char *buf, unsigned int len,=20
			            MP_INT *value)
        {
          unsigned int i;
          MP_INT aux;
          mpz_init_set(&aux, value);
          for (i = len; i >= 4; i -= 4)   <-------
            {
              unsigned long limb = mpz_get_ui(&aux);
              PUT_32BIT(buf + i - 4, limb);   <-------
              mpz_div_2exp(&aux, &aux, 32);
            }
          [...]
        }

    There's the overflow! len is  the length of the encrypted  session
    key,  while  buf  is  a   pointer  to  the  fixed  length   buffer
    input_data[MAX_RSA_MODULUS_LEN]  and  no   check  wether  len   is
    greater than MAX_RSA_MODULUS_LEN is performed.  The fix should  be
    obvious!

    In  this  particular  overflow,  the  encrypted,  client generated
    session  key  has  to  be  taken  as  the exploit buffer. I.e. the
    shellcode, NOPs and jump address has to sent to the server instead
    of  the   encrypted  session   key.  To   make  that   clear:  The
    shellcode, NOPs  and jump  address don't  have to  be encrypted as
    they are taken as the ENCRYPTED session key.

    However, the data that is finally written into the buffer are  the
    limbs of  the multiple  precision integer  that session_key_int is
    assumed to be. The exploit buffer code therefore must be converted
    into a multiple  precision integer, which  upon extraction of  the
    limbs into the buffer yields the correct exploit buffer code.  The
    best way would probably be to start from the exploit buffer as  it
    should  finally  be  to  overflow  the  target  buffer and use the
    functions  of  the  GNU  multiple  precision  integer  library  to
    reverse the procedure  happening to the  encrypted session key  in
    the sshd code step be step, leading to the exploit buffer that has
    to be sent instead of the encrypted session key.

SOLUTION

    And here's a patch.  Not tested, as author doesn't use the  rsaref
    glue on any machine:

        --- rsaglue.c.orig	Tue Nov  9 11:12:32 1999
        +++ rsaglue.c	Tue Nov  9 11:17:58 1999
        @@ -139,6 +139,10 @@

           input_bits = mpz_sizeinbase(input, 2);
           input_len = (input_bits + 7) / 8;
        +  if(input_bits > MAX_RSA_MODULUS_BITS)
        +    fatal("Attempted to encrypt a block too large (%d bits, %d max) (malicious?).",
        +    	input_bits, MAX_RSA_MODULUS_BITS);
        +
           gmp_to_rsaref(input_data, input_len, input);

           rsaref_public_key(&public_key, key);
        @@ -172,6 +176,10 @@

           input_bits = mpz_sizeinbase(input, 2);
           input_len = (input_bits + 7) / 8;
        +  if(input_bits > MAX_RSA_MODULUS_BITS)
        +    fatal("Received session key too long (%d bits, %d max) (malicious?).",
        +    	input_bits, MAX_RSA_MODULUS_BITS);
        +
           gmp_to_rsaref(input_data, input_len, input);

           rsaref_private_key(&private_key, key);

    Debian is  immune for  the (somewhat  messy) reasons  that they do
    not  link  ssh  to  rsaref.   Note  that  OpenSSH  is immune, too.
    It does _not_  use rsaglue.c for  rsaref.  In  FreeBSD, you should
    be using the "OpenSSH-1.2" package, ports/security/openssh.   This
    is a direct port of the  OpenSSH source from the OpenBSD CVS,  and
    as  such  is  that  much  more  secure than plain SSH, and OpenSSH
    should be used instead where possible.