2013
06.04

Summary

At the beginning of May I found and reported a security vulnerability in Coinbase, a Bitcoin exchange. The vulnerability I reported allowed an attacker to steal the CSRF token for the currently logged in user, which meant that an attacker could bypass the site’s CSRF protection. Coinbase acknowledged the report and fixed the underlying vulnerability.

Details

Several URLs on coinbase.com used to return valid JavaScript which was intended to populate modal dialog boxes on the site. One example of such a URL was https://coinbase.com/api_key. The content of those dialog boxes was generated dynamically based on the currently logged in user.

If the dialog box contained a form, the complete HTML for that form was included in the response. That HTML included a hidden form field (authenticity_token) which is the CSRF token used by the site for that user. Because these URLs returned valid JavaScript via a GET request, a third-party website (whether malicious or benign) could extract the HTML for the dialog box, including the CSRF token.

For example, here is what I saw when I browsed to https://coinbase.com/api_key:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
$('#api_key_modal').html("<div class=\"modal-header\">\n
  <button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-hidden=\"true\">&times;<\/button>\n
  <h4>Show API Key<\/h4>\n
<\/div>\n
<div class=\"modal-body\">\n
  <form accept-charset=\"UTF-8\" action=\"/api_key/authenticate\" class=\"form-inline\" data-remote=\"true\" method=\"post\">
<div style=\"margin:0;padding:0;display:inline\"><input name=\"utf8\" type=\"hidden\" value=\"&#x2713;\" />
<input name=\"authenticity_token\" type=\"hidden\" value=\"qGeCEw6MkTPZt2dLZJ43ftXmR36gYYNXWRwywKcsxE4=\" />
<\/div>\n
    \n
    \n
    <p>Please type your password to continue.<\/p>\n
    <p>\n
      <input class=\"focus\" id=\"password\" name=\"password\" placeholder=\"password\" type=\"password\" />\n
      <a href=\"#\" class=\"btn btn-primary submit-form\" data-disable-with=\"Verify\">Verify<\/a>\n
    <\/p>\n
<\/form><\/div>\n
<div class=\"modal-footer\">\n
  <a href=\"#\" class=\"btn\" data-dismiss=\"modal\">Close<\/a>\n
<\/div>");
$('#api_key_modal').modal('show');

Other sensitive information could also be disclosed under limited circumstances. For example, if the user had authenticated to see their API key the dialog box would not have prompted for authentication, as it does above, but instead would return the account’s API key.

The solution here was simple: the server-side scripts should not have been returning valid JavaScript as part of a GET request. The Coinbase developers have modified all of their dialog endpoints to require a POST request, which prevents them from being loaded via a <script> tag.

Disclosure Timeline

  • May 1st, 3:18 PM: Submitted basic details of vulnerability to whitehat@coinbase.com.
  • May 1st, 9:56 PM: Sent clarification of initial report, including additional information about CSRF leakage.
  • May 6th, 5:03 PM: Sent followup email, asking for feedback on the initial report.
  • May 6th, 5:04 PM: Provided additional vulnerable URL, as well as clarification of the initial report.
  • May 7th, 12:24 AM: Received reply: “Thank you for the disclosure, we appreciate it. This issue was already reported by others and we are working on a fix.”
  • May 19th, 6:44 PM: Informed Coinbase of intentions to publish this blog post on May 28th.
  • May 28th, 9:00 AM: Discovered that developers had attempted to patch the website but that they had forgotten to remove an endpoint.
  • May 28th, 9:29 AM: Emailed Coinbase to inform them of the incomplete patch. Informed them that publication of the blog post would be delayed by a week so they would have time to fix the vulnerability.
  • June 4th, 12:00 AM: Confirmed that final endpoint had been patched.
  • June 4th, 12:45 AM: Public disclosure via blog post

Comments