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.