Tarsnap email confirmation bypass
Over the past four years, Tarsnap's bug bounties have received quite a bit of attention. Most of it has been very useful — almost 400 mistakes (most either cosmetic or harmless, but some of them significant) have been reported and fixed — but it does also get some unwanted attention: Despite my clear statement that Tarsnap's bug bounties are for problems in tarsnap code, not for problems in the website, I regularly see people running automated vulnerability scanners... which invariably yield a selection of absurd non-vulnerability "vulnerabilities".
One consequence of these unsolicited security scans is that —
since they feed a variety of inputs to forms, including the account
creation form — I see a lot of obviously fake signup attempts
(alas, none yet from
the world's most obviously fake domain
name). These are harmless, since the signup code sends out a
confirmation email and the account isn't actually created until the
alleged registrant follows a link in that email; so I wasn't
concerned when I received an email last week telling me that someone
was trying to create an account as admin@tarsnap.com
.
Five minutes later, I was very concerned upon receiving an email
telling me that the registration for admin@tarsnap.com
had been confirmed and the account created.
This should not have happened, so I immediately started running down
a list of possibilities. Was it a forged email? No, the headers
showed it being delivered from the CGI script to the tarsnap web
server's qmail to the tarsnap mail server's qmail to my inbox. Was
a copy of the confirmation email — which should never have
gotten past the mail server — being misdelivered somehow? No,
the mail logs showed that the email to admin@tarsnap.com
went from CGI script to the web server's qmail to the mail server's
qmail and then was dropped. Was one of the CGI scripts on the tarsnap
web server compromised? There was nothing in the logs to suggest a
malformed
request of the sort which might have been used to exploit a bug; nor,
for that matter, anything to suggest that someone had been probing for
bugs, so if a CGI script had been exploited, it was done completely
blindly. Nevertheless, I disabled the CGI scripts just in case.
Had someone managed to compromise the web server or mail server? I saw no unexpected processes running; nothing abnormal in the logs; and neither server had rebooted recently (as might be required to install a kernel root kit). Of course you can't rely on anything from within a system to reliably diagnose whether it has been compromised, and I wasn't running auditdistd, so I couldn't be absolutely certain... but realistically, if an attacker was sophisticated enough to compromise either system without leaving any trace, how likely was it that they would blow their cover by creating an obviously impossible tarsnap account?
The mystery was solved a few minutes later when an email arrived from Elamaran Venkatraman: He hadn't compromised any servers or exploited any bugs in my C code; rather, he had found a dumb mistake in tarsnap's account-creation process.
For most people to create a Tarsnap account, only a few things are required: An email address, a password, and checkbox confirming that you agree to the Tarsnap legal boilerplate. You submit those to the Tarsnap server; it generates a registration cookie; it sends that cookie to you as part of a URL in the confirmation email; and when you click through that link and re-enter your password your account is created. So far so good — but some people need a bit more than that. Tarsnap is a Canadian company, and as such is required to remit sales tax for its Canadian-resident customers. Moreover, Tarsnap is required to issue invoices to its Canadian-resident customers — invoices which show the customers' physical mailing addresses — so if a registrant identifies themself as being a Canadian resident, they are taken to a second page to provide their name and mailing address.
But what of that confirmation email? Well, I didn't want someone who self-identified as a Canadian resident to create an account without providing the legally-mandated information, so I couldn't send out that email until they submitted the second page. On the other hand, they having provided their email address and password once already, I didn't want to ask for those again. And so, when I finally got all the paperwork sorted and started accepting Canadian customers in July 2012, I took the option which was simple, obvious and completely wrong: I passed the registration cookie as a hidden variable in the second-page form, to be echoed back to the server.
This of course is what Elamaran had found. To be clear, the registration cookie didn't reveal any server internals; the only thing it could be used for was to confirm an email address. But because it was being sent in the HTML response, anyone could "confirm" any email address, simply by claiming to be a Canadian resident and viewing the page source. Oops.
The fix for this was easy: Use two cookies, one for email confirmation and one for the Canadian-address-obtaining continuation. More importantly, I've moved the cookie-generation to where it belongs — within the routine which generates and sends the confirmation email — and I've added a comment to remind myself that the cookie must never be exposed via any channel other than an email destined for the address being confirmed.
That last part is ultimately the most important lesson from this: Comments matter! I don't know what I was thinking three years ago when I reused that cookie; but unless my memory was much better then than it is now, I almost certainly wasn't thinking about my original design from four years prior. While this was hardly a fatal bug — while I'll never know for certain, I doubt anyone exploited this email confirmation bypass, and the impact would not be severe even if someone did — it's an reminder of the importance of writing useful comments. I often see solo developers excuse a lack of comments in their code on the basis that they understand their code and nobody else will be touching it; this misses an essential point: I am not the same person as I was three years ago, nor do I understand everything I understood three years ago.
People make mistakes, and people edit code without fully understanding how and why it works. Leave breadcrumbs behind, even if you don't intend for anyone to follow you: When you try to retrace your steps, you might get lost without them.