How to lock a GitHub user out of their repos (bug or feature?)

We accidentally discovered this while working with our friends from X41, when GitHub denied me access to my private repositories; for example git pull of git clone would fail and write “ERROR: Permission to <account>/<repo>.git denied to deploy key”. I asked for help but nobody found the root cause.

Here’s the problem: GitHub recently introduced deploy keys, or keys granting read-only right to a server to best automate deployment scripts. This is a great feature, however it can be abused as follows to lock a GitHub user out of their repositories:

  1. Find one or more SSH public keys belonging to the victim, but not associated to their GitHub account. For example, you may happen to know SSH keys associated to another project, or you may use public keys from https://gitlab.com/username.keys (comparing with https://github.com/username.keys).
  2. Add these SSH public keys as deploy keys of one of your GitHub projects (in Settings > Deploy keys > Add deploy key). Let’s say this project is at github.com/attacker/repo.
  3. When connecting to GitHub over SSH, the victim will then be identified at attacker/repo (for example when doing ssh -T [email protected]), if and only if at least of the keys added as deploy keys is prioritized by SSH over any key linked to the GitHub account.

For example, if you have private key files id_ecdsa_github and id_ecdsa_gitlab in your ~/.ssh/ directory, and if SSH offers the public key id_ecdsa_gitlab.pub first, and if the attacker has added that key as deploy key, then GitHub will identify you as attacker/repo when you’ll try to connect to one of your repositories, thereby denying you access to your private repositories (and only granting read access to public ones).

The way SSH prioritizes key can be found in its code, and also varies depending on whether you use ssh-agent (typically, when caching passphrase-protected private keys).

This behavior of GitHub and SSH can therefore be exploited to lock GitHub users out of their private repositories (via command-line SSH), and arguably qualifies as a denial-of-service attack. Note that it doesn’t require any action from the victim, and only needs public information (if we assume that public keys are public). We’ve lost one afternoon of work investigating the issue, being involuntarily attacked by X41. After understanding what happened, we asked them to remove our (freshly generated) key from their repo’s deploy keys, which indeed solved the issue.

You can argue that it’s the responsibility of users to properly configure their ~/.ssh/config, which of course avoids the attack, and that that behavior is acceptable. This is presumably the opinion of GitHub, since they responded to our bug bounty entry that “it does not present a security risk”. GitHub probably has good reasons to ignore the risk, but from our perspective the potential annoyance and DoS risk to GitHub users is not be negligible.

The problem can probably be fixed, although it’s not be straightforward, since SSH authentication would have to depend not only on the host name, but also on the repository accessed (whereas access control to a repository is typically enforced after SSH authentication).

Update: As @stuartpb noticed, this behavior can also be exploited by adding a victim’s public keys to a user account (new or repo-less), and is not specific to deploy keys.