2010/06/29
github home | http://github.com/mcarpenter/time_ssh_auth |
---|---|
repository URLs |
https://github.com/mcarpenter/time_ssh_auth.git git://github.com/mcarpenter/time_ssh_auth.git |
time_ssh_auth
records the time for
SSH authentication to fail against a host. Many SSH daemon
implementations vary in the time taken to reject a valid
user with invalid credentials compared to the time taken to reject an
invalid (unknown) user. That gives us a way to enumerate
usernames.
If you're thinking of using this for black-hatting then you will get noticed: this technique is really noisy. However if you're a white hat with a black box on your desk then this might be useful.
Vendors of "appliances" tend to be fussy about not-yet-customers peeling off "removal voids your warranty" stickers from loaner boxes and pulling out the disks. Your organization might be fussy about such excavation once you've bought the kit. How can you get a list of system users as part of your security evaluation?
Often such appliances are white-badged servers running
some free UNIX variant. Sometimes, they use the system authentication
system (PAM) for web-GUI/application authentication (perhaps
especially where a command-line interface (ssh
or
telnet
) is also available). Enumerating system usernames
by timing system authentication failures might then give you application
usernames. (Alternatively, can application credentials be used for system
level access?).
Passwords are also theoretically obtainable by performing character-by-character timing comparisons (due to poorly implemented "short circuit" comparison or hash functions). This requires an exponential increase in computation (time) and more advanced statistical analysis.
An early cut in Python/Paramiko produced surprisingly good results against a PBX. I rewrote it in C and libssh for speed and to use real-time clock facilities.
The program attempts a given number of authentications
with a given fixed password (-p
) or identity file
(-i
). (If the authentication actually succeeds then
that's perhaps the end of your evaluation...). The time for each
authentication attempt is output in nanoseconds and at the end of the run
the median average is computed for each user. We use the median
since the mean can be adversely affected by a small number
of significant outliers (eg. network glitch or CPU spike). The median
is less susceptible to noise (statisticians say it is more robust).
If you are lucky there will be two distinct groups of timings: valid
and invalid users. You probably know at least one valid user
(eg. root
) and your test should include usernames that are
extremely unlikely to exist on the target.
The following results were obtained against a Western Digital MyBook, World Edition (a cheap Linux-on-ARM "home SAN") with 1000 password login attempts per user:
$ ./sshtime -p password mybook 1000 daemon bin sync operator \ nobody default guest root martin bogus1 bogus2 bogus3 bogus4 \ bogus5 >mybook.out 2>mybook.err $ $ fgrep median mybook.out | sort -n -k3 daemon median 27426402 bin median 27536087 sync median 27659413 operator median 27904556 nobody median 28006044 bogus4 median 32301348 bogus5 median 32311740 bogus3 median 32322276 bogus2 median 32340496 bogus1 median 32354843 default median 32690818 guest median 32723860 root median 46010003 martin median 47073363 $
For reference here is the shadow(4)
file (hashes elided):
# cat /etc/shadow root:$1$hash:10933:0:99999:7::: daemon:*:10933:0:99999:7::: bin:*:10933:0:99999:7::: sync:*:10933:0:99999:7::: operator:*:10933:0:99999:7::: nobody:*:10933:0:99999:7::: default:!:10933:0:99999:7::: guest:!$1$hash:10933:0:99999:7::: martin:$1$hash:10933:0:99999:7::: #
And some conclusions:
root
and martin
) took significantly longer to be rejected.shadow(4)
was
preserved in the sorted timings: each extra line in passwd(4)
/shadow(4)
costs an extra ~150 000 ns (0.00015 s) authentication time.
passwd(4)
and so the timings are all very close together.default
and guest
have a bang in
their password field: apparently this is parsed quite differently from
star since it takes considerably longer to reject these logins.
The last point remains a mystery. The GNU/Debian Linux
man-page for shadow(4)
only suggests that these characters
are invalid and that invalid strings will preclude the user from logon.
If the password field contains some string that is not valid result of crypt(3), for instance ! or *, the user will not
be able to use a unix password to log in, subject to pam(7).
This is supported by the source code, modules/pam_unix/passverify.c
in PAM 1.0.1:
} else if (!p || *hash == '*' || *hash == '!') { retval = PAM_AUTH_ERR;I suspect that this behaviour is peculiar to the MyBook's Linux distribution.
It's a little surprising that the obvious countermeasure — to make the authentication step always take exactly the same amount of time whether it passes or fails — is not implemented in most (any?) SSH servers. But the risk here is tiny: the attack is fantastically noisy and the most that an attacker can reasonably gain is a list of valid usernames which might in any case be public or observable by other simpler means.
Other countermeasures are more widely deployed:
Of those, the first is by far the most prolific and such an attack is therefore mostly likely to provoke a DoS in short order.
Crosby, Wallach, & Riedi, Opportunities and Limits of Remote Timing Attacks, ACM Trans. Inf. Syst. Secur. 12, 3, Article 17 (January 2009).