hashing known_hosts
Robert Drake
rdrake at sprint.net
Tue Mar 4 12:19:39 EST 2003
Scenario:
I have access to a semi-public (about 30 users) server where I keep my
webpage. Occasionally, especially if I'm on the road. I use this as a
bounce point to get to "secured" systems which only allow ssh from
certian IP's. (Ignoring the discussion on spoofing, since we have host
keys)
But host keys are the problem. If anyone gets root on this hypothetical
public server they have 30 known_hosts files they can look through to find
new hosts to go after. I didn't like that, so my first instinct was to
delete my known_hosts file on logout, but then I lose all the benifits of
host keys.
So how about this?
We should hash the <host>,<ip> pair and store that in the known_hosts file.
Then someone has to know the hash to figure out what host key goes to what.
Drawbacks:
If a host is only known by IP (no hostname) the attacker only has to check a
maximum of 4 billion hashes. I'm running through these now to see how long
it takes and how big the dictionary file is.
You could get around this by using salt, but since you have no reference you
have to check every hash in the known_host file, which could be expensive if
you have 300 host keys.
Alternatively, you can use DNS everywhere (which you should be doing anyway)
since that gives you a hostname,xx.xx.xx.xx hash message, which makes
dictionary attacks much less feasible.
Another drawback is that it makes key management a bitch, since you can't
search you're known_hosts file by hostname. I consider this a problem that
can be addressed by ethier an external program, or by adding it into ssh.
Personally I think ssh should prompt you for more options when a key comes
up as invalid, something like "Host key not found. Do you want to accept
new key?" Of course, I'd also like the ability to sign old host keys just
to make the trust model safer.
I also think this should be an optional configuration option. Something
like "HashKnownHosts yes"
Test code..
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <openssl/evp.h>
#include <openssl/sha.h>
int
uuencode(u_char *src, u_int srclength,
char *target, size_t targsize)
{
return b64_ntop(src, srclength, target, targsize);
}
static char *pt(unsigned char *md)
{
int i;
static char buf[80];
for (i=0; i<SHA_DIGEST_LENGTH; i++)
sprintf(&(buf[i*2]),"%02x",md[i]);
return(buf);
}
int main(void) {
int a,b,c,d;
char ip[16];
unsigned char md[SHA_DIGEST_LENGTH];
char uu[SHA_DIGEST_LENGTH*2];
/* for (a=1; a<=233;a++) { */
for (a=1; a<=5;a++) {
for(b=0;b<=255;b++) {
for(c=0;c<=255;c++) {
for(d=0;d<=255;d++) {
sprintf(ip, "%d.%d.%d.%d", a,b,c,d);
SHA1(ip,strlen(ip),md);
uuencode(md, SHA_DIGEST_LENGTH, uu, SHA_DIGEST_LENGTH*2);
printf("%s %s\n", ip, uu);
}
}
}
}
return 0;
}
I'm only doing the first 5 "class A" ranges of IPs. We can use this to
calculate how long it would take to look at all 4 billion IPs.
revolution:~$ time ./test1 > /dev/null
real 43m44.440s
user 19m20.701s
sys 0m3.442s
revolution:~$ time ./test1 > test-dict.out
real 47m53.129s
user 20m23.721s
sys 1m8.202s
revolution:~$ ls -l test-dict.out
-rw-r--r-- 1 rdrake other 3498967040 Aug 20 11:42 test-dict.out
revolution:~$ ls -l dict.gz
-rw-r--r-- 1 rdrake other 2044302219 Aug 20 13:42 dict.gz
So, for 2% we have 2 gigs of disk space for the dictionary. A dictionary
attack is obviously not too practicle, but definately feasible (especially
if they can download the known_hosts file to their machine)
Problem: It's hard to remove old hosts because you don't know which ones
have changed.
Solution: timestamp each entry with a "last accessed time". Any host that
hasen't been logged into in over xxx time is later removed (or processed in
some other way)
Of course, if you wanted effective host management, you could always store
the hostname/sha hash pairs in a public-key encrypted file. Then the user
would type their passphrase and have access to the hostnames. Adds a whole
asston of complications to ssh key management, but alot of it could be in an
external program.
I wrote this initial email about 6 months ago and finally got around
to making a patch. Please let me know if this would be an interesting
addition, or if I'm just a retard.
Thanks,
Robert
--
Robert Drake
-------------- next part --------------
Common subdirectories: openssh-3.4p1/autom4te-2.53.cache and openssh-3.4p1-hash/autom4te-2.53.cache
Common subdirectories: openssh-3.4p1/contrib and openssh-3.4p1-hash/contrib
diff -u -p openssh-3.4p1/hostfile.c openssh-3.4p1-hash/hostfile.c
--- openssh-3.4p1/hostfile.c Thu Dec 20 20:47:09 2001
+++ openssh-3.4p1-hash/hostfile.c Mon Mar 3 17:28:25 2003
@@ -135,8 +135,13 @@ check_host_in_hostfile(const char *filen
;
/* Check if the host name matches. */
+#ifdef HASH_KNOWN_HOSTS
+ if (match_hashed_hostname(host, cp, (u_int) (cp2 - cp)) != 1)
+ continue;
+#else
if (match_hostname(host, cp, (u_int) (cp2 - cp)) != 1)
continue;
+#endif
/* Got a match. Skip host name. */
cp = cp2;
diff -u -p openssh-3.4p1/match.c openssh-3.4p1-hash/match.c
--- openssh-3.4p1/match.c Mon Mar 4 20:42:43 2002
+++ openssh-3.4p1-hash/match.c Mon Mar 3 17:32:17 2003
@@ -176,6 +176,14 @@ match_hostname(const char *host, const c
return match_pattern_list(host, pattern, len, 1);
}
+#ifdef HASH_KNOWN_HOSTS
+int
+match_hashed_hostname(const char *host, const char *pattern, u_int len)
+{
+ return match_pattern_list(host, pattern, len, 0);
+}
+#endif
+
/*
* returns 0 if we get a negative match for the hostname or the ip
* or if we get no match at all. returns 1 otherwise.
diff -u -p openssh-3.4p1/match.h openssh-3.4p1-hash/match.h
--- openssh-3.4p1/match.h Mon Mar 4 20:42:43 2002
+++ openssh-3.4p1-hash/match.h Mon Mar 3 17:30:44 2003
@@ -20,5 +20,8 @@ int match_hostname(const char *, const
int match_host_and_ip(const char *, const char *, const char *);
int match_user(const char *, const char *, const char *, const char *);
char *match_list(const char *, const char *, u_int *);
+#ifdef HASH_KNOWN_HOSTS
+int match_hashed_hostname(const char *, const char *, u_int);
+#endif
#endif
Common subdirectories: openssh-3.4p1/openbsd-compat and openssh-3.4p1-hash/openbsd-compat
diff -u -p openssh-3.4p1/readconf.c openssh-3.4p1-hash/readconf.c
--- openssh-3.4p1/readconf.c Thu Jun 20 20:41:52 2002
+++ openssh-3.4p1-hash/readconf.c Mon Mar 3 17:33:00 2003
@@ -114,6 +114,9 @@ typedef enum {
oDynamicForward, oPreferredAuthentications, oHostbasedAuthentication,
oHostKeyAlgorithms, oBindAddress, oSmartcardDevice,
oClearAllForwardings, oNoHostAuthenticationForLocalhost,
+#ifdef HASH_KNOWN_HOSTS
+ oHashKnownHosts,
+#endif
oDeprecated
} OpCodes;
@@ -186,6 +189,9 @@ static struct {
{ "smartcarddevice", oSmartcardDevice },
{ "clearallforwardings", oClearAllForwardings },
{ "nohostauthenticationforlocalhost", oNoHostAuthenticationForLocalhost },
+#ifdef HASH_KNOWN_HOSTS
+ { "hashknownhosts", oHashKnownHosts },
+#endif
{ NULL, oBadOption }
};
@@ -380,6 +386,12 @@ parse_flag:
intptr = &options->check_host_ip;
goto parse_flag;
+#ifdef HASH_KNOWN_HOSTS
+ case oHashKnownHosts:
+ intptr = &options->hash_known_hosts;
+ goto parse_flag;
+#endif
+
case oStrictHostKeyChecking:
intptr = &options->strict_host_key_checking;
arg = strdelim(&s);
@@ -793,6 +805,9 @@ initialize_options(Options * options)
options->bind_address = NULL;
options->smartcard_device = NULL;
options->no_host_authentication_for_localhost = - 1;
+#ifdef HASH_KNOWN_HOSTS
+ options->hash_known_hosts = -1;
+#endif
}
/*
@@ -907,6 +922,10 @@ fill_default_options(Options * options)
clear_forwardings(options);
if (options->no_host_authentication_for_localhost == - 1)
options->no_host_authentication_for_localhost = 0;
+#ifdef HASH_KNOWN_HOSTS
+ if (options->hash_known_hosts == -1)
+ options->hash_known_hosts = 0;
+#endif
/* options->proxy_command should not be set by default */
/* options->user will be set in the main program if appropriate */
/* options->hostname will be set in the main program if appropriate */
diff -u -p openssh-3.4p1/readconf.h openssh-3.4p1-hash/readconf.h
--- openssh-3.4p1/readconf.h Sun Jun 9 16:04:03 2002
+++ openssh-3.4p1-hash/readconf.h Wed Aug 21 11:44:59 2002
@@ -100,6 +100,9 @@ typedef struct {
Forward remote_forwards[SSH_MAX_FORWARDS_PER_DIRECTION];
int clear_forwardings;
int no_host_authentication_for_localhost;
+#ifdef HASH_KNOWN_HOSTS
+ int hash_known_hosts;
+#endif
} Options;
Common subdirectories: openssh-3.4p1/regress and openssh-3.4p1-hash/regress
Common subdirectories: openssh-3.4p1/scard and openssh-3.4p1-hash/scard
diff -u -p openssh-3.4p1/sshconnect.c openssh-3.4p1-hash/sshconnect.c
--- openssh-3.4p1/sshconnect.c Sun Jun 23 17:23:20 2002
+++ openssh-3.4p1-hash/sshconnect.c Mon Mar 3 17:34:04 2003
@@ -17,6 +17,11 @@ RCSID("$OpenBSD: sshconnect.c,v 1.126 20
#include <openssl/bn.h>
+#ifdef HASH_KNOWN_HOSTS
+#include <openssl/sha.h>
+#include "uuencode.h"
+#endif
+
#include "ssh.h"
#include "xmalloc.h"
#include "rsa.h"
@@ -505,6 +510,11 @@ check_host_key(char *host, struct sockad
char msg[1024];
int len, host_line, ip_line;
const char *host_file = NULL, *ip_file = NULL;
+#ifdef HASH_KNOWN_HOSTS
+ unsigned char md[SHA_DIGEST_LENGTH];
+ char uu[SHA_DIGEST_LENGTH*2];
+#endif
+
/*
* Force accepting of the host key for loopback/localhost. The
@@ -579,6 +589,26 @@ check_host_key(char *host, struct sockad
* hosts or in the systemwide list.
*/
host_file = user_hostfile;
+
+#ifdef HASH_KNOWN_HOSTS
+ if (options.hash_known_hosts) {
+ /*
+ * turn off host ip checking because we take care of it
+ */
+ options.check_host_ip = 0;
+
+ snprintf(hostline, sizeof(hostline), "%s,%s", host,ip);
+ SHA1(hostline, strlen(hostline), md);
+ uuencode(md, SHA_DIGEST_LENGTH, uu, SHA_DIGEST_LENGTH*2);
+ host_status = check_host_in_hostfile(host_file, uu, host_key,
+ file_key, &host_line);
+ if (host_status == HOST_NEW) {
+ host_file = system_hostfile;
+ host_status = check_host_in_hostfile(host_file, uu,
+ host_key, file_key, &host_line);
+ }
+ } else {
+#endif
host_status = check_host_in_hostfile(host_file, host, host_key,
file_key, &host_line);
if (host_status == HOST_NEW) {
@@ -586,6 +616,10 @@ check_host_key(char *host, struct sockad
host_status = check_host_in_hostfile(host_file, host, host_key,
file_key, &host_line);
}
+
+#ifdef HASH_KNOWN_HOSTS
+ } /* end if options.hash_known_hosts */
+#endif
/*
* Also perform check for the ip address, skip the check if we are
* localhost or the hostname was an ip address to begin with
@@ -662,6 +696,10 @@ check_host_key(char *host, struct sockad
if (options.check_host_ip && ip_status == HOST_NEW) {
snprintf(hostline, sizeof(hostline), "%s,%s", host, ip);
hostp = hostline;
+#ifdef HASH_KNOWN_HOSTS
+ } else if (options.hash_known_hosts) {
+ hostp = uu;
+#endif
} else
hostp = host;
More information about the openssh-unix-dev
mailing list