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 usernamekb_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: anythingusername: 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.

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.