Date: Thu, 5 Nov 1998 02:38:51 +0200 From: Tatu Ylonen 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 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 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 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