Date: Thu, 5 Nov 1998 02:38:51 +0200
From: Tatu Ylonen <ylo@SSH.FI>
To: BUGTRAQ@netspace.org
Subject: security patch for ssh-1.2.26 kerberos code

-----BEGIN PGP SIGNED MESSAGE-----

This message contains information relevant to people who compile ssh
with --with-kerberos5.  There is one or more potential security
problem in the Kerberos code.  These issues are not relevant for
people who have not explicitly specified --with-kerberos5 on the
configure command line.

Peter Benie <pjb1008@cam.ac.uk> found a buffer overflow in the
kerberos authentication code.  To quote from his mail:

> What about sshconnect.c, line 1139
>
>     sprintf(server_name,"host/%s@", remotehost);
>
> where remotehost is (char *) get_canonical_hostname() (up to 255 chars),
> is copied into server_name (a 128 char buffer)?

It looks to me like this is a genuine buffer overflow.  I had not
noticed it when going through the code.

This buffer overflow is, however, extremely hard to exploit:
  1. The victim must have have client compiled with --with-kerberos5 and
     --enable-kerberos-tgt-passing.
  2. The victim must be connecting to a server running with the same
     options (i.e., krb5 with tgt passing).
  3. You must do the following DNS spoofing:
       - fake reverse map for the *server*
       - fake forward map for the fake reversed name
  4. You must fake your attack code to look like valid DNS records; this
     is highly untrivial with modern versions of bind that reject all
     domain names with invalid characters in them.
  5. Only the part of the DNS name beyond 128 bytes can be exploited; that
     must be made to align with stack frames and must contain appropriate
     return addresses and jump addresses.  It has been shown that this can
     generally be done, but the space and structural constraints here are
     extremely tight compared to most instances of buffer overflow
     exploits.
  6. Since the client with Kerberos TGT passing is only used
     interactively, the user will almost certainly notice that something
     went wrong.  I don't think you can, within the structure and space
     constraints, construct the code so that the user would not notice at
     least the client crashing.
  7. You cannot try again after a failed attack until the client again
     tries to log into the same host.

This might yield an attack against the *client*.

I've fixed this in the source tree.

I'd like to thank Peter for reporting this.  A fix will be included in
the next release (which I expect in about a week).

The patch for this problem is quote simple:
- --- sshconnect.c.orig Thu Nov  5 02:09:55 1998
+++ sshconnect.c        Thu Nov  5 02:10:53 1998
@@ -1090,7 +1090,7 @@
   krb5_data outbuf;
   krb5_error_code r;
   int type;
- -  char server_name[128];
+  char server_name[512];

   remotehost = (char *) get_canonical_hostname();
   memset(&outbuf, 0 , sizeof(outbuf));
@@ -1136,7 +1136,7 @@
        principal and point it to clients realm. This way
        we pass over a TGT of the clients realm. */

- -    sprintf(server_name,"host/%s@", remotehost);
+    sprintf(server_name,"host/%.100s@", remotehost);
     strncat(server_name,client->realm.data,client->realm.length);
     krb5_parse_name(ssh_context,server_name, &server);
     server->type = KRB5_NT_SRV_HST;

However, there are also a few other potential problems in the Kerberos
code.  Special thanks to Barry Irwin <bvi@rucus.ru.ac.za> for pointing
out one of them.  I'm not yet sure whether they are exploitable (we
don't have Kerberos installed here, nor documentation for it, so I
can't easily check; however, they might be more serious than the
above).  I'd recommend everyone using ssh with Kerberos authentication
compiled in to install the following full patch, just to make sure.
In addition to the above, it explicitly restricts the length of
various strings.  This also includes the above.

    Tatu

- --- sshconnect.c.orig Thu Nov  5 02:09:55 1998
+++ sshconnect.c        Thu Nov  5 02:10:53 1998
@@ -282,7 +282,7 @@

       /* Child.  Permanently give up superuser privileges. */
       if (setuid(getuid()) < 0)
- -     fatal("setuid: %s", strerror(errno));
+       fatal("setuid: %.100s", strerror(errno));

       /* Redirect stdin and stdout. */
       close(pin[1]);
@@ -944,7 +944,7 @@
   if (!ssh_context)
     {
       if ((r = krb5_init_context(&ssh_context)))
- -     fatal("Kerberos V5: %s while initializing krb5.", error_message(r));
+       fatal("Kerberos V5: %.100s while initializing krb5.", error_message(r));
       krb5_init_ets(ssh_context);
     }

@@ -959,14 +959,14 @@
                                   "host", KRB5_NT_SRV_HST,
                                   &creds.server)))
     {
- -      debug("Kerberos V5: error while constructing service name: %s.",
+      debug("Kerberos V5: error while constructing service name: %.100s.",
            error_message(r));
       goto cleanup;
     }
   if ((r = krb5_cc_get_principal(ssh_context, ccache,
                                 &creds.client)))
     {
- -      debug("Kerberos V5: failure on principal (%s).",
+      debug("Kerberos V5: failure on principal (%.100s).",
            error_message(r));
       goto cleanup;
     }
@@ -975,7 +975,7 @@
   if ((r = krb5_get_credentials(ssh_context, 0,
                                ccache, &creds, &new_creds)))
     {
- -      debug("Kerberos V5: failure on credentials(%s).",
+      debug("Kerberos V5: failure on credentials(%.100s).",
            error_message(r));
       goto cleanup;
     }
@@ -987,7 +987,7 @@
     {
       if ((r = krb5_auth_con_init(ssh_context, &auth_context)))
        {
- -       debug("Kerberos V5: failed to init auth_context (%s)",
+         debug("Kerberos V5: failed to init auth_context (%.100s)",
                error_message(r));
          goto cleanup;
         }
@@ -998,7 +998,7 @@
   if ((r = krb5_mk_req_extended(ssh_context, &auth_context, ap_opts,
                                0, new_creds, &auth)))
     {
- -      debug("Kerberos V5: failed krb5_mk_req_extended (%s)",
+      debug("Kerberos V5: failed krb5_mk_req_extended (%.100s)",
            error_message(r));
       goto cleanup;
     }
@@ -1046,7 +1046,7 @@

       if (r = krb5_rd_rep(ssh_context, auth_context, &auth, &repl))
        {
- -       packet_disconnect("Kerberos V5 Authentication failed: %s",
+         packet_disconnect("Kerberos V5 Authentication failed: %.100s",
                            error_message(r));
          goto cleanup;
        }
@@ -1090,7 +1090,7 @@
   krb5_data outbuf;
   krb5_error_code r;
   int type;
- -  char server_name[128];
+  char server_name[512];

   remotehost = (char *) get_canonical_hostname();
   memset(&outbuf, 0 , sizeof(outbuf));
@@ -1100,14 +1100,14 @@
   if (!ssh_context)
     {
       if ((r = krb5_init_context(&ssh_context)))
- -     fatal("Kerberos V5: %s while initializing krb5.", error_message(r));
+       fatal("Kerberos V5: %.100s while initializing krb5.", error_message(r));
       krb5_init_ets(ssh_context);
     }
   if (!auth_context)
     {
       if ((r = krb5_auth_con_init(ssh_context, &auth_context)))
        {
- -       debug("Kerberos V5: failed to init auth_context (%s)",
+         debug("Kerberos V5: failed to init auth_context (%.100s)",
                error_message(r));
          return 0 ;
         }
@@ -1124,7 +1124,7 @@
     if ((r = krb5_cc_get_principal(ssh_context, ccache,
                                    &client)))
       {
- -        debug("Kerberos V5: failure on principal (%s)",
+        debug("Kerberos V5: failure on principal (%.100s)",
              error_message(r));
         return 0 ;
       }
@@ -1136,7 +1136,7 @@
        principal and point it to clients realm. This way
        we pass over a TGT of the clients realm. */

- -    sprintf(server_name,"host/%s@", remotehost);
+    sprintf(server_name,"host/%.100s@", remotehost);
     strncat(server_name,client->realm.data,client->realm.length);
     krb5_parse_name(ssh_context,server_name, &server);
     server->type = KRB5_NT_SRV_HST;
@@ -1145,7 +1145,7 @@
     if ((r = krb5_fwd_tgt_creds(ssh_context, auth_context, 0, client,
                                server, ccache, 1, &outbuf)))
       {
- -     debug("Kerberos V5 krb5_fwd_tgt_creds failure (%s)",
+       debug("Kerberos V5 krb5_fwd_tgt_creds failure (%.100s)",
              error_message(r));
        krb5_free_principal(ssh_context, client);
         krb5_free_principal(ssh_context, server);
@@ -1416,7 +1416,7 @@
       error("Someone could be eavesdropping on you right now (man-in-the-middle attack)!");
       error("It is also possible that the host key has just been changed.");
       error("Please contact your system administrator.");
- -      error("Add correct host key in %s to get rid of this message.",
+      error("Add correct host key in %.100s to get rid of this message.",
            options->user_hostfile);

       /* If strict host key checking is in use, the user will have to edit
@@ -1589,7 +1589,7 @@
   if (!ssh_context)
     {
       if ((problem = krb5_init_context(&ssh_context)))
- -     fatal("Kerberos V5: %s while initializing krb5.",
+       fatal("Kerberos V5: %.100s while initializing krb5.",
              error_message(problem));
       krb5_init_ets(ssh_context);
     }
@@ -1605,7 +1605,7 @@
          if ((problem = krb5_cc_get_principal(ssh_context, ccache,
                                               &client)))
            {
- -           debug("Kerberos V5: failure on principal (%s).",
+             debug("Kerberos V5: failure on principal (%.100s).",
                     error_message(problem));
            }
          else {
- --- auth-kerberos.c.orig      Thu Nov  5 02:06:02 1998
+++ auth-kerberos.c     Thu Nov  5 02:08:14 1998
@@ -63,11 +63,11 @@
          krb5_auth_con_free(ssh_context, auth_context);
          auth_context = 0;
        }
- -      log_msg("Kerberos ticket authentication of user %s failed: %s",
+      log_msg("Kerberos ticket authentication of user %.100s failed: %.100s",
              server_user, error_message(problem));

- -      debug("Kerberos krb5_auth_con_genaddrs (%s).", error_message(problem));
- -      packet_send_debug("Kerberos krb5_auth_con_genaddrs: %s",
+      debug("Kerberos krb5_auth_con_genaddrs (%.100s).", error_message(problem));
+      packet_send_debug("Kerberos krb5_auth_con_genaddrs: %.100s",
                        error_message(problem));
       return 0;
     }
@@ -80,11 +80,11 @@
          krb5_auth_con_free(ssh_context, auth_context);
          auth_context = 0;
        }
- -      log_msg("Kerberos ticket authentication of user %s failed: %s",
+      log_msg("Kerberos ticket authentication of user %.100s failed: %.100s",
              server_user, error_message(problem));

- -      debug("Kerberos V5 rd_req failed (%s).", error_message(problem));
- -      packet_send_debug("Kerberos V5 krb5_rd_req: %s", error_message(problem));
+      debug("Kerberos V5 rd_req failed (%.100s).", error_message(problem));
+      packet_send_debug("Kerberos V5 krb5_rd_req: %.100s", error_message(problem));
       return 0;
     }

@@ -93,22 +93,22 @@
   if (problem)
     {
       krb5_free_ticket(ssh_context, ticket);
- -      log_msg("Kerberos ticket authentication of user %s failed: %s",
+      log_msg("Kerberos ticket authentication of user %.100s failed: %.100s",
              server_user, error_message(problem));

- -      debug("Kerberos krb5_unparse_name failed (%s).", error_message(problem));
- -      packet_send_debug("Kerberos krb5_unparse_name: %s",
+      debug("Kerberos krb5_unparse_name failed (%.100s).", error_message(problem));
+      packet_send_debug("Kerberos krb5_unparse_name: %.100s",
                        error_message(problem));
       return 0;
     }
   if (strncmp(server, "host/", strlen("host/")))
     {
       krb5_free_ticket(ssh_context, ticket);
- -      log_msg("Kerberos ticket authentication of user %s failed: invalid service name (%s)",
+      log_msg("Kerberos ticket authentication of user %.100s failed: invalid service name (%.100s)",
              server_user, server);

- -      debug("Kerberos invalid service name (%s).", server);
- -      packet_send_debug("Kerberos invalid service name (%s).", server);
+      debug("Kerberos invalid service name (%.100s).", server);
+      packet_send_debug("Kerberos invalid service name (%.100s).", server);
       krb5_xfree(server);
       return 0;
     }
@@ -122,11 +122,11 @@

   if (problem)
     {
- -      log_msg("Kerberos ticket authentication of user %s failed: %s",
+      log_msg("Kerberos ticket authentication of user %.100s failed: %.100s",
              server_user, error_message(problem));
- -      debug("Kerberos krb5_copy_principal failed (%s).",
+      debug("Kerberos krb5_copy_principal failed (%.100s).",
            error_message(problem));
- -      packet_send_debug("Kerberos krb5_copy_principal: %s",
+      packet_send_debug("Kerberos krb5_copy_principal: %.100s",
                        error_message(problem));
       return 0;
     }
@@ -135,11 +135,11 @@
   /* Make the reply - so that mutual authentication can be done */
   if ((problem = krb5_mk_rep(ssh_context, auth_context, &reply)))
     {
- -      log_msg("Kerberos ticket authentication of user %s failed: %s",
+      log_msg("Kerberos ticket authentication of user %.100s failed: %.100s",
              server_user, error_message(problem));
- -      debug("Kerberos krb5_mk_rep failed (%s).",
+      debug("Kerberos krb5_mk_rep failed (%.100s).",
            error_message(problem));
- -      packet_send_debug("Kerberos krb5_mk_rep failed: %s",
+      packet_send_debug("Kerberos krb5_mk_rep failed: %.100s",
                        error_message(problem));
       return 0;
     }
@@ -160,7 +160,7 @@
 {
   krb5_creds **creds;
   krb5_error_code retval;
- -  static char ccname[128];
+  static char ccname[512];
   krb5_ccache ccache = NULL;
   struct passwd *pwd;
   extern char *ticket;
@@ -208,9 +208,9 @@

   if (retval = krb5_rd_cred(ssh_context, auth_context, krb5data, &creds, NULL))
     {
- -      log_msg("Kerberos V5 tgt rejected for user %.100s : %s", server_user,
+      log_msg("Kerberos V5 tgt rejected for user %.100s : %.100s", server_user,
              error_message(retval));
- -      packet_send_debug("Kerberos V5 tgt rejected for %.100s : %s",
+      packet_send_debug("Kerberos V5 tgt rejected for %.100s : %.100s",
                        server_user,
                        error_message(retval));
       packet_start(SSH_SMSG_FAILURE);
@@ -234,7 +234,7 @@
     goto errout;

   ticket = xmalloc(strlen(ccname) + 1);
- -  (void) sprintf(ticket, "%s", ccname);
+  (void) sprintf(ticket, "%.100s", ccname);

   /* Successful */
   packet_start(SSH_SMSG_SUCCESS);
@@ -244,9 +244,9 @@

 errout:
   krb5_free_tgt_creds(ssh_context, creds);
- -  log_msg("Kerberos V5 tgt rejected for user %.100s :%s", server_user,
+  log_msg("Kerberos V5 tgt rejected for user %.100s :%.100s", server_user,
          error_message(retval));
- -  packet_send_debug("Kerberos V5 tgt rejected for %.100s : %s", server_user,
+  packet_send_debug("Kerberos V5 tgt rejected for %.100s : %.100s", server_user,
                    error_message(retval));
   packet_start(SSH_SMSG_FAILURE);
   packet_send();


- --
SSH Communications Security           http://www.ssh.fi/
SSH IPSEC Toolkit                     http://www.ipsec.com/
Free Unix SSH                         http://www.ssh.fi/sshprotocols2/

-----BEGIN PGP SIGNATURE-----
Version: 2.6.3i
Charset: noconv

iQCVAwUBNkDyOakZxfGWH0o1AQGYOQP/bUNnE/ZpSQqWVc0ngxLG50+CtyksugLJ
wD0X2yIoc8jmY+UNPL7weQatgv6CmUUoWWpLctzKr8A6G/HrD2sh0OHPBwhIxg1i
3mPj7WrcIX9g/K5LaEksiZ0vv4h/gvSJty5y+wRiu0QLRmuAy91CyaKTV7Sab0YT
/W/s1NazNIg=
=iABB
-----END PGP SIGNATURE-----

----------------------------------------------------------------

Date: Wed, 4 Nov 1998 13:50:49 +0100
From: Joel Eriksson <na98jen@STUDENT.HIG.SE>
To: BUGTRAQ@netspace.org
Subject: Re: ssh-1.2.26 buffer overflow patch

On Sun, 1 Nov 1998, Andy Church wrote:

> [login.c:538]
> log_msg("putuserattr S_LASTTTY %.900s failed: %.100s", /*...*/);
>
> [log-server.c:130]
> void log_msg(const char *fmt, ...)
> {
>   char buf[1024];
>   /*...*/
>   vsprintf(buf, fmt, args);
>   /*...*/
> }
>
> Granted, I haven't checked whether this is exploitable, but I could never
> call that secure.  (Count the bytes.)

I don't think there's any reason of not using snprintf, but, in this
particular case they seem to be rather well protected anyway. I have
noticed the line you mention above, and a few others that woke my
interest. But since the whole log_msg-call reads:

        log_msg("putuserattr S_LASTTTY %.900s failed: %.100s",
            ttyname, strerror(errno));

It means that strerror() must return a string with a length around 100
bytes *and* ttyname should be around 900 bytes, I have not checked if that
is possible, but I certainly don't think so.

There are a few other places that made me wonder, like when lines like:

          log_msg("Connection for %.200s not allowed from %s\n",
                  user, get_canonical_hostname());

were used, when tracing back to get_canonical_hostname() and then back to
get_remote_hostname() it seems as the name is limited to 255 bytes anyway.

In auth-kerberos.c there are frequent use of %s in the log_msg()-calls and
it sure looks dangerous.. In the places where I have tracked the origin of
the strings supplied their length have been checked *before* log_msg() is
used. I think that IBM's statements about not having found any exploitable
overflows in ssh-1.2.26 can probably be trusted, but since the functions
for logging uses vsprintf to a buffer on 1024 bytes it certainly is very
possible to introduce a severe securityhole if not being careful. It
certainly seems a lot easier to me to use vsnprintf's instead and limit
the length to 1023 bytes instead of having to track back every use of the
functions to be certain no securityproblem exists.

>   --Andy Church                    | If Bell Atlantic really is the heart
>     achurch@dragonfire.net         | of communication, then it desperately
>     http://achurch.dragonfire.net/ | needs a quadruple bypass.

/Joel Eriksson
