January 10, 2023
whoarethey: Determine Who Can Log In to an SSH Server
Filippo Valsorda has a neat SSH server
that reports the GitHub username of the connecting client. Just SSH to whoami.filippo.io, and if you're
a GitHub user, there's a good chance it will identify you. This works
because of two behaviors: First, GitHub publishes your authorized public
keys at https://github.com/USERNAME.keys
. Second,
your SSH client sends the server the public key of every one of your
key pairs.
Let's say you have three key pairs, FOO
, BAR
, and BAZ
.
The SSH public key authentication protocol works like this:
Client: Can I log in with public key
FOO
?
Server looks forFOO
in ~/.ssh/authorized_keys; finds no match
Server: No
Client: Can I log in with public keyBAR
?
Server looks forBAR
in ~/.ssh/authorized_keys; finds no match
Server: No
Client: Can I log in with public keyBAZ
?
Server looks forBAZ
in ~/.ssh/authorized_keys; finds an entry
Server: Yes
Client: OK, here's a signature from private keyBAZ
to prove I own it
whoami.filippo.io
works by taking each public key sent by the client
and looking it up in a map from public key to GitHub username, which Filippo populated
by crawling the GitHub API. If it finds a match, it tells the client the GitHub username:
Client: Can I log in with public key
FOO
?
Server looks upFOO
, finds no match
Server: No
Client: Can I log in with public keyBAR
?
Server looks upBAR
, finds no match
Server: No
Client: Can I log in with public keyBAZ
?
Server looks upBAZ
, finds a match to user AGWA
Server: Aha, you're AGWA!
This works the other way as well: if you know that AGWA's public keys
are FOO
, BAR
, and BAZ
, you can send each of them to the server to see if
the server accepts any of them, even if you don't know the private keys:
Client: Can I log in with public key
FOO
?
Server: No
Client: Can I log in with public keyBAR
?
Server: No
Client: Can I log in with public keyBAZ
?
Server: Yes
Client: Aha, AGWA has an account on this server!
This behavior has several implications:
If you've found a server that you suspect belongs to a particular GitHub user, you can confirm it by downloading their public keys and asking if the server accepts any of them.
If you want to find servers belonging to a particular GitHub user, you could scan the entire IPv4 address space asking each SSH server if it accepts any of the user's keys. This wouldn't work with IPv6, but scanning every IPv4 host is definitely practical, as shown by masscan and zmap.
If you've found a server and want to find out who controls it, you can try asking the server about every GitHub user's keys until it accepts one of them. I'm not sure how practical this would be; testing every GitHub user's keys would require sending an enormous amount of traffic to the server.
As a proof of concept, I've created whoarethey, a small Go program that takes the hostname:port of an SSH server, an SSH username, and a list of GitHub usernames, and prints out the GitHub username which is authorized to connect to the server. For example, you can try it on a test server of mine:
$ whoarethey 172.104.214.125:22 root github:AGWA github:FiloSottile github:AGWA
whoarethey
reports that I, but not Filippo, can log into root@172.104.214.125.
You can also use whoarethey
with public key files stored locally, in which
case it prints the name of the public key file which is accepted:
Note that just because a server accepts a key (or claims to
accept a key), it doesn't mean that the holder of the private
key authorized the server to accept it. I could take Filippo's public key
and put it in my authorized_keys
file, making it look
like Filippo controls my server. Therefore, this information leak doesn't provide
incontrovertible proof of server control.
Nevertheless, I think it's a useful way to deanonymize a server,
and it concerns me much more than whoami.filippo.io
.
I only SSH to servers which already know who I am, and
I'm not very worried about being tricked into connecting
to a malicious server - it's not like the Web where it's trivial
to make someone visit a URL.
However,
I do have accounts on a few servers which are not otherwise
linkable to me, and it came as an unpleasant surprise that anyone
would be able to learn that I have an account just by asking the
SSH server.
The simplest way to thwart whoarethey
would be for SSH servers to refuse to answer if a particular public key would be accepted, and instead make clients pick
a private key and send the signature to the server. Although I don't know of any SSH servers that can be configured
to do this, it could be done within the bounds
of the current SSH protocol. The user experience would be the same for people who use a single key per client, which I assume
is the most common configuration. Users with multiple keys would need
to tell their client which key they want to use for each server, or
the client would have to try every key, which might require the user to enter a
passphrase or press a physical button for each attempt.
(Note that to prevent a timing leak, the server should verify the signature
against the public key provided by the client before checking if the
public key is authorized. Otherwise, whoarethey
could determine if a public key is authorized by sending an
invalid signature and measuring how long it takes the server to reject it.)
There's a more complicated solution (requiring protocol changes and fancier cryptography) that leverages private set intersection
to thwart both whoarethey
and whoami.filippo.io
. However, it treats SSH keys as encryption
keys instead of signing keys, so it wouldn't work with hardware-backed keys like the YubiKey. And it requires
the client to access the private key for every key pair, not just the one accepted by the server, so the user experience for
multi-key users would be just as bad as with the simple solution.
Until one of the above solutions is implemented, be careful if you administer any servers which you don't want
linked to you. You could use unique key pairs for such servers, or keep SSH firewalled
off from the Internet and connect over a VPN. If you do use a unique key pair, make sure
your SSH client never tries to send it to other servers - a less benign version of whoami.filippo.io
could save
the public keys that it sees, and then feed them to whoarethey to find your servers.
Post a Comment
Your comment will be public. To contact me privately, email me. Please keep your comment polite, on-topic, and comprehensible. Your comment may be held for moderation before being published.
Comments
Reader Samuel BF on 2023-01-11 at 23:08:
2 remarks for OSINT hackers :
- gitlab also lists public keys of users ( https://docs.gitlab.com/ee/api/users.html#list-ssh-keys-for-user ). It can make the database more comprehensive and may reveal matching between accounts on several platforms (de-anonymisation). I have not searched other forges.
- for GPG-backed SSH keys, you can retrieve SSH public key with --generate-ssh-key ( https://www.gnupg.org/faq/whats-new-in-2.1.html#sshexport ). Now, you have email addresses in your database.
All in all, these API represent a significant leak of PII !
Reply
Andrew Ayer on 2023-01-15 at 17:56:
Great tip about GitLab!
I don't think what you say about GPG is correct. The SSH public key format just contains key material and has no space for an email address. The textual representation does have a comment field which GPG could place an email address in, but GitHub omits the comment (as does GitLab based on the API examples).
Reply
Reader HacKan on 2023-01-20 at 00:22:
Well, the easiest solution is to use independent SSH keys for everything, particularly for GH. I have individual keys per device, and they are also unique to GH, so no SSH server has them. I thinks that's the most viable solution, although I do now have a shit ton of keys to manage :P
Reply
Andrew Ayer on 2023-01-20 at 23:13:
That doesn't sound very easy, and is particularly infeasible when using hardware-backed keys.
Reply
Reader ValdikSS on 2023-02-26 at 16:03:
The simpliest way is just to use unique unpredictable user and not add autorized_keys for root.
Reply
Andrew Ayer on 2023-03-02 at 13:17:
Users should not have to work around problems that can be fixed by the implementation.
Reply