Hacker News new | ask | show | jobs
by keithwinstein 1345 days ago
I agree this seems to be the currently in vogue practice, but I am confused by it!

SSH keys are public-private key pairs, where the private portion can live in a hardware token or software agent that only signs individual challenges and never exposes the private key. (It's possible in theory, although admittedly not common, for the user agent to limit approvals to a particular repository using the techniques in https://github.com/StanfordSNR/guardian-agent . It would be nicer if GitHub would enforce granular permissions on SSH keys!)

If I understand right, HTTP access tokens are... bearer tokens where I have to handle the plaintext (so I can transmit it to GitHub) every time I want to use it? I agree it's nice to have GitHub enforcing more granular permissions, but even so the trade really doesn't seem worth it. I may not be getting something here.

1 comments

The main reason to use HTTP access tokens is just for the more flexible user-facing functionality. Security-wise, the issue is (to me) not totally clear-cut.

Yes, with HTTP access tokens, you do have to transmit the plaintext each time. (OIDC would be much better, but The Industry Powers That Be decided that non-interactive networked clients had no need to benefit from federated identity and temporary tokens) But the attack vectors (broadly) are 1. at-rest storage (credential managers are good enough, I think) and 2. HTTPS MITM. If the attacker can somehow manage an HTTPS MITM, they get the plaintext token, and can then attack that account offline for as long as the token is valid. It's definitely not easy to HTTPS MITM, but the protocol is much larger and more complicated than SSH, so vulnerabilities are more likely. State actors can do it trivially, of course (though this wouldn't be the case if we hadn't thrown HTTP Client Certificates out with the bathwater!!).

SSH private keys are technically superior, because like you say, the private key can be unexposed (if you use a hardware token etc). This means that the attack vector left is SSH MITM. If the attacker can pull this off, they can only attack when you are connecting, because they don't have your private key to perform arbitrary offline attacks. But how likely is MITM? Well, when was the last time you verified the host keys of GitHub.com? How did you verify it? Are you absolutely sure it was correct, and the host keys transmitted during your initial SSH connection weren't a MITM? Even if they were correct, can the attacker still MITM you? Well, what if they disconnect each of your connection attempts, until you try from a different user/computer, or just remove or disable your host key cache? They might re-try the attack then, and hope you don't verify the host keys this time. All this assuming you didn't globally remove the host key checks in your SSH config (for example, if you connect to a lot of work hosts and you got annoyed by host key verification so you disabled it? maybe some automation script in a ci/cd process is doing this?)

Ultimately the decision of which to use rests with you. Personally, I already rely on HTTPS for most of my security on the internet, so I might as well rely on it for my code, and gain some extra functionality/convenience. But I do wish the HTTPS method used OIDC, and the SSH method had more fine-grained controls.

> This means that the attack vector left is SSH MITM. If the attacker can pull this off, they can only attack when you are connecting, because they don't have your private key to perform arbitrary offline attacks.

Not only that, but that MITM attack could only present modified data to your client; even a successful MITM attacker still cannot authenticate to github as if it were you. The authentication is symmetric, each side authenticates the other.

(A very simplified explanation, the real protocol is a bit more complex: each side combines its private key with the other side's public key, and due to some math the results are identical on both sides; the resulting number is used as a cryptographic key in the protocol. Since the attacker doesn't have access to your private key, it cannot combine it with github's real public key to obtain the cryptographic key github expects. It cannot "pass through" to you the real github public key, because then it wouldn't know the corresponding private key which it needs to do the MITM.)

Thanks for this analysis! To me the risk of bearer tokens seems bigger than just "at-rest storage"... My experience is that the security perimeter effectively ends up including every script or other piece of client software that wants to run Git or has access to the channel where the bearer token is entered. (How would you enforce that your credentials manager will only give the bearer token to some particular trusted HTTPS client software that is talking only to GitHub...?) You have to worry about the token getting logged in a debugging log, etc. And, when I give somebody an account that's authenticated with a bearer token, I'm always nervous maybe their hygiene is not so great and they'll end up accidentally pushing the token somewhere publicly visible. AFAIK you don't really have this attack surface when the credential is a private key stored in a hardware token or, at worst, a software agent that only keeps the decrypted version in private memory and only agrees to sign individual challenges.

On the question of HTTPS MITM vs. SSH MITM, I agree that the HTTPS PKI makes a MITM less likely than the (prevalent) use of TOFU for SSH authentication, but I don't think the risks (or what "MITM" means) are the same. If there's an HTTPS MITM when client authentication is via bearer token, the intermediary can steal the client's token and masquerade as them forever. If there's an SSH MITM when client authentication is via public key, then the intermediary can give bogus data to the client and can get data from the client, but to the best of my knowledge I don't think a true application-layer "MITM" is possible (barring downgrade attacks) -- the intermediary can't spin around and authenticate to GitHub, pretending to be the client. The session ID is part of the authentication scheme.

> How would you enforce that your credentials manager will only give the bearer token to some particular trusted HTTPS client software that is talking only to GitHub...?

I mean, if a client has enough privilege to be able to connect to my credentials manager in the first place, it's already game over. Whatever can execute that client can probably execute arbitrary code, and then use a privilege escalation vuln, so they can do whatever they want on the machine.

> You have to worry about the token getting logged in a debugging log, etc. And, when I give somebody an account that's authenticated with a bearer token, I'm always nervous maybe their hygiene is not so great and they'll end up accidentally pushing the token somewhere publicly visible

Yes, completely valid concern. Not that people don't already push their private SSH keys onto GitHub repos or Jira tickets/email, but that's a lot easier to detect and prevent. But on balance, the bearer is like a password; if the user has any other passwords you care about, you already have to deal with these concerns. The bearer is therefore a "slightly better" password, because you can set an expiration date, limit the scopes, rotate it easier, it's not reused on multiple sites, etc. MFA helps but of course is not a panacea.

And yes you're right with your second paragraph too. (I tried to mention it in my earlier comment but it wasn't clear) I do think that public-key crypto is much better security, but that it's harder for the user to get right, and people have underestimated the feasibility of SSH attacks. I think we're just lucky that there are much easier attacks than MITM so we haven't seen a rash of them. It's much easier to inject malware via a browser, or find an open ACL or network service, etc.