Breaking Credit Card Tokenization – Part 3

By Tim MalcomVetter ·

In the first two installments of this blog series I addressed the core terms and history of credit card tokenization systems before digging into some attacks. Please read part one and part two to get up to speed on general terms and background information before digging into new attacks discussed in this post.

Side Channels

The previous post in this blog discussed timing side channel attacks on credit card tokenization systems. This post will investigate the following side channel attacks:

  1. Credit cards saved to user profiles in an e-commerce application
  2. Helpful HTTP headers in RESTful credit card tokenization services

My Profile Side Channel Attacks

Many commerce apps—especially ones using credit card tokenization—implement a “My Profile” type feature in which the customer can save a form of payment for future reuse.  If an attacker can take over a customer’s account or session (e.g. by stealing credentials or if the application switches between HTTP and HTTPS with the same session cookies making MITM possible), then the attacker can browse to the “My Saved Credit Cards” page, observe truncated PAN data, and then proceed to add new credit cards to the user account’s profile, analyzing the responses.  

For example, suppose Alice, a user on the e-commerce site, has three saved credit cards in her user profile, and an attacker who has stolen her session or credentials can observe the following:

Visa ending in 1111 Exp 12/17 Alice Smith
123 Smith St
Beverly Hills, CA 90210
MasterCard ending in 1324 Exp 11/19 Alice Smith
123 Smith St
Beverly Hills, CA 90210
American Express ending in 4993 Exp 04/18 Alice Smith
Smith Enterprises
456 Smith Ave
New York, NY 10001


This metadata is not quite as helpful as the data a malicious insider in the first post in this series may have, but it is still enough to hone in on specific attacks. All of the potential credit card prefixes for Visa, MasterCard, and American Express are open source knowledge for a given target country. An attacker can use this information to generate a list of potential PANs that match this very truncated version, and, one at a time, attempt to add them to Alice’s profile. If a fourth payment method is on the list, then the attacker did not discover one of Alice’s credit cards. However, if the number of payment methods stays the same, then the attacker just discovered one of Alice’s credit cards. The following pseudo-code illustrates the algorithm (award bonus points for deleting the bogus credit card that didn’t match what the application already had in the database):

def findPan(truncatedPan):
    pans = generatePansFromTruncation(truncatedPan)
    count = countCCsInProfile()
    foreach pan in pans:
        addCcToProfile(pan)
        newCount = countCCsInProfile()
        if (count == newCount):
            return pan // Hit!
        else:
            removeCcFromProfile(pan)
    return

Behind the scenes, this is another example of developers doing what they are trained to do: be efficient. If a credit card tokenization record already exists, then adding a duplicate violates the efficiency rules developers are taught. Thus, another side channel attack is discovered.

Preventing My Profile Attacks

The fix to this, like the fix in the second post in this series, is to intentionally introduce some inefficiency in the application in lieu of creating a mechanism to extract a Boolean true/false response whether a credit card already exists. A suggestion we often provide to developers is to return a new payment method ID or token even if the credit card matches a record already in the database.

Helpful Header Side Channels

Some developers who really like REST can be downright fanatical about RESTful design requirements, splitting hairs over whether a particular implementation is truly RESTful or not.  Many large development teams have at least one of these “RESTful service geeks” (my moniker for them), but do not let them design your RESTful credit card tokenization service.  Consider the following tokenization request from the first post in this series:

POST /api/generateCcToken HTTP/1.1
Host: fakeretailpaymentprovider.com
Connection: keep-alive
Accept: */*
Content-Type: application/json
Content-Length: 161

{"authToken":"VGhpcyBpcyBqdXN0IGFuIGV4YW1wbGUuIEEgcmVhbCBvbmUgd291bGQgYmUgYmV0dGVyLg==","cc":"4111111111111111","expmm":"12","expyyyy":"2017"}

A RESTful service geek will instruct developers that HTTP status codes should be very distinct. For example, the service might return the following response:

HTTP/1.1 201 Created
Content-Type: application/json 
[…snip…]

Or perhaps this:

HTTP/1.1 202 Accepted
Content-Type: application/json 
[…snip…]

Something as small as “201” versus “202” can indicate a world of contextual difference. An attacker will likely learn that “201 Created” means the credit card in the tokenization request has not previously been seen by the tokenization system, so the system created a brand new credit card token record for it. Likewise, an attacker might decipher “202 Accepted” as an indication that the credit card already exists on the system. Or perhaps there are custom HTTP headers that are added or updated on the service’s response (which may especially be true if the service is designed for the consumption by a mobile application where developers think nobody will ever be able to see those headers).  Any of these can be enough of a signal, no matter how subtle—just take the blinders off and look carefully at every detail. We have seen this attack’s cousin for years now: web applications that are kind enough to tell us if a user account already exists at registration or during an attempt to remember a user ID or password.  

This insight, coupled with the scenarios already presented in this series where an attacker may know truncated PANs and billing information, can also lead to credit card exfiltration.

Preventing Header Side Channel Attacks

Successful applications will always respond the same way regardless of the internal differences.  It’s boring and probably doesn’t comply with all of the RESTful principles, but at the same time it protects knowledge of the presence of certain credit cards stored in the system. The following is a nice safe response:

HTTP/1.1 200 OK
Content-Type: application/json 
[…snip…]

Stay Tuned

Stay tuned for more attacks against credit card tokenization systems in the rest of this blog series.

Read part 4