A Low-Impact Keybase Impersonation Issue on Lobsters

3 hours ago 3

June 8, 2025

A couple of weeks ago I found and reported a minor security issue in Lobsters. It’s since been patched and almost certainly was never exploited. As I’ll briefly discuss later, there wouldn’t even be much to gain by it. Here’s a description of the issue and how it could be exploited.

Issue

Keybase is a service that, apparently amongst other things, allows you to cryptographically prove that you control accounts on other services. There’s a much nicer UI on top of it, but to my understanding at its core you are signing a statement like “I own the account ~chamlis on Lobsters” with a private key whose public counterpart is published through Keybase. None of the cryptographic details matter here, however.

To check the other direction, that a given Keybase account has proven that it controls a Lobsters account, Lobsters used the following function:

def self.proof_valid?(kb_username, kb_signature, username) s = Sponge.new url = [ File.join(base_url, "/_/api/1.0/sig/proof_valid.json?"), "domain=#{Rails.application.credentials.keybase.domain}&", "kb_username=#{kb_username}&", "sig_hash=#{kb_signature}&", "username=#{username}" ].join("") res = s.fetch(url, :get).body js = JSON.parse(res) js && js["proof_valid"].present? && js["proof_valid"] end

As per Keybase’s proof integration guide, we make a request to Keybase to check a proof with a given signature (kb_signature) is valid for the given Keybase account (kb_username) and Lobsters account (username). If the proof is valid, Lobsters displays the Keybase account on the user’s profile page.

The URL used for this request is constructed manually via string concatenation, which means it may be possible to inject additional data. For example, if username was alice&foo=bar, this would appear verbatim in the final URL, the resulting query string specifying username as alice and foo as bar.

Exploitation

Of the inputs to this function, kb_username and kb_signature are controlled by us, and username is always our Lobsters username. We could technically control the latter, but there are more limits on valid usernames, and whatever we changed it to would appear in the moderation log, which wouldn’t go unnoticed.

We therefore have no control over the URL until after kb_username=. This means we can’t affect anything before the query string, like replacing the API method or the host we’re calling. We also can’t affect the domain parameter, so we can still only check Keybase proofs relevant for Lobsters accounts. We could try adding an additional domain parameter, but from my testing it seems Keybase will say a proof is invalid if any query keys are repeated, even if they have the same value each time.

One thing we can do is to attach an existing Keybase proof for a different Lobsters account to our one. Given an existing proof we can reuse the kb_username and kb_signature which will still be valid—we only need to replace the username. As before, we can inject an extra spoofed username parameter, but we will never get a valid result while the correct one is also present.

After the query string in a URL comes the fragment, which is delimited by a # character. We can inject a # to essentially comment out everything after that point in the URL, removing the extra username parameter. This doesn’t let us use an additional domain parameter, as the existing one is before we can insert anything.

We also mustn’t forget to URL encode our parameters, so they are still correctly parsed from our request before being used in the vulnerable function.

All told, we can attach a valid Keybase proof for a target account by adding these query parameters to the Lobsters page for a new Keybase proof (which was https://lobste.rs/keybase_proofs/new):

  • kb_username: the target’s Keybase username
  • kb_signature: the target’s Keybase signature hash, followed by %26username%3D (&username= URL-encoded), followed by the target’s Lobsters username, followed by %23 (# URL-encoded)
  • kb_ua: anything
  • username: your Lobsters username

Reporting and Resolution

I reported this issue to Peter Bhat Harkins, the administrator of Lobsters. He immediately acknowledged it and we discussed how best to address it. He pointed out that Keybase did not seem very active, and that it might be easiest to simply remove the Keybase integration. After reaching out to a sample of users with Keybase accounts linked, he reported there did not seem to be much community attachment to it. Some time later he removed the Keybase integration on Lobsters office hours. All-in-all, my report was taken seriously and addressed promptly—a much more positive reporting experience than some I’ve had in the past.

If retaining Keybase functionality were desirable, the alternative fix would be to properly escape the strings used to construct the Keybase API call. This could either be done manually, or with a library that constructs the query string from an associative array rather than directly using its input.

Impact

To really drive home just how low-impact this issue is, let’s sketch out what we’d need to do something truly malicious with this:

  • A target Lobsters user with an associated Keybase account (quite rare)
  • A proof tying that Lobsters user to that Keybase account (even amongst those with a live Keybase account, the proof had quite often been revoked immediately after linking the two)
  • Our own Lobsters account

From there, we can spoof the Keybase field on our own Lobsters profile. But it’s unclear what you could do beyond that point. Trying to fool someone with only the Keybase field on your profile as “proof” doesn’t seem very fruitful to me, especially when any other means they might try to verify your identity would fail. Maybe if you had a constellation of other spoofed identity proofs you could achieve something, but that seems even more unlikely. I’ve never had a Keybase account, but I certainly wouldn’t lose any sleep over this if I did.

Read Entire Article