Skip to Content [alt-c]

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 for FOO in ~/.ssh/authorized_keys; finds no match
Server: No
Client: Can I log in with public key BAR?
Server looks for BAR in ~/.ssh/authorized_keys; finds no match
Server: No
Client: Can I log in with public key BAZ?
Server looks for BAZ in ~/.ssh/authorized_keys; finds an entry
Server: Yes
Client: OK, here's a signature from private key BAZ 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 up FOO, finds no match
Server: No
Client: Can I log in with public key BAR?
Server looks up BAR, finds no match
Server: No
Client: Can I log in with public key BAZ?
Server looks up BAZ, 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 key BAR?
Server: No
Client: Can I log in with public key BAZ?
Server: Yes
Client: Aha, AGWA has an account on this server!

This behavior has several implications:

  1. 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.

  2. 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.

  3. 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:

$ whoarethey 172.104.214.125:22 root agwa.pub filosottile.pub agwa.pub

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.

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

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.

(Optional; will be published)

(Optional; will not be published)

(Optional; will be published)

  • Blank lines separate paragraphs.
  • Lines starting with > are indented as block quotes.
  • Lines starting with two spaces are reproduced verbatim (good for code).
  • Text surrounded by *asterisks* is italicized.
  • Text surrounded by `back ticks` is monospaced.
  • URLs are turned into links.
  • Use the Preview button to check your formatting.