We recently began emailing 3,420 Coinbase customers to let them know that a bug on our signup page resulted in some registration details being stored in clear text in our internal web server logs. While we are confident that we’ve fixed the root cause and that the logged information was not improperly accessed, misused, or compromised, we are requiring those customers to change their passwords as a best-practice precaution.

Here’s what happened

Under a very specific and rare error condition, the registration form on our signup page wouldn’t load correctly, which meant that any attempt to create a new Coinbase account under those conditions would fail. Unfortunately, it also meant that the individual’s name, email address, and proposed password (and state of residence, if in the US) would be sent to our internal logs.

If the individual reloaded the page and then submitted the form for a successful registration, their registration information would (correctly) not be logged, and the password would be securely hashed. However, in the 3,420 instances referenced above, the user successfully registered using a password with a hash that matched the one previously logged.

Here’s how we responded

After we identified and fixed the bug, we traced back all the places where these logs might have ended up. We have an internal logging system hosted in AWS, as well as a small number of log analysis service providers. Access to all of these systems is tightly restricted and audited. A thorough review of access to these logging systems did not reveal any unauthorized access to this data. Additionally, we triggered a password reset for impacted customers, even though a password alone is not sufficient to access a Coinbase account — our device verification emails and mandatory 2FA mechanisms would both have been triggered and blocked any unauthorized login attempts.

The technical details

The Coinbase front-end web app is built using the React.js framework and leverages React’s Server Side Rendering (SSR) for key pages (including /signup). SSR means that, instead of sending a barebones HTML page and depending on React to fully populate it in the browser, we send a partly rendered page from the server and let React put the finishing touches on in the browser. We use SSR in order to give searchable content to search engine crawlers, which generally don’t run Javascript. Any user attempting to register needs to have JavaScript enabled, and needs to have that JavaScript load correctly. In virtually all circumstances, both of these things are true, and React handles form validation and submission to the server. However, if a user had JavaScript disabled or their browser received a React.js error when loading, there was enough pre-rendered HTML that a user could fill out and attempt to submit our registration form.

The HTML form itself was extremely basic, since we expected that the details would be filled in by React: <form>[list of fields]</form>. This had two implications for default behavior:

Because no “action” attribute was set, the official behavior defined in the W3C specification was “undefined”. In practice, however, some browsers will set “action” to the current URI and make a best effort to submit the form. Because the “method” was not set, per W3C specification the browser defaulted to GET, which meant that, all form variables were URL encoded and appended as a query string to submit data to the server, e.g in the pattern /signup?first_name=Alice&last_name=Smith&email=alice%40example.com&password=password

This query string then ended up in our logs. The fix itself was simple and straightforward: we changed the default form method to POST, as data submitted in the request body (instead of a query string) is not logged: <form method=”POST”>[list of fields]</form>. We also searched the rest of our repository for any other forms with that problematic behavior, and did not identify any. We’re also in the process of implementing additional mechanisms to detect and prevent the inadvertent introduction of this sort of bug in the future.

Conclusion

We maintain incredibly high standards for securing the Coinbase platform, and any time we fall even slightly short of those standards, we mobilize a team to figure out what went wrong, and how we prevent it from happening again. We also believe in being transparent with our customers, which is why we’re sharing the results of our investigation today.

As a reminder, Coinbase also maintains an active bug bounty program on HackerOne, which has paid out over a quarter of a million dollars to date: https://hackerone.com/coinbase. While this particular bug was discovered internally, we welcome security researchers to submit reports any time they believe they may have uncovered a flaw in one of our systems.