Breaking Credit Card Tokenization – Part 4

By Tim MalcomVetter ·

This is a continuation of a series of blog posts on the topic of breaking credit card tokenization systems. I recommend reading the first three parts of the series if you have any questions about credit card tokenization terms or concepts.

Careless Transmission of Credit Cards

Remember that the main point of credit card tokenization is to keep PANs (Primary Account Numbers) out of the main application-hosting environment. Merchants accomplish this by transmitting the PAN from the customer’s browser directly to a tokenization server in a special PCI DMZ where the litany of expensive and restricted PCI controls is fully applicable. Keeping the PAN out of the hosting environment minimizes costs and restrictions on the main application’s hosting environment, which is why anyone would want to implement tokenization (ahem, cough … of course it also reduces risk, right?). However, in my experience with multiple client engagements (and admittedly even once or twice in a past life as a developer integrating e-commerce applications against payment service gateway’s tokenization APIs), subtle code flaws have allowed the PANs to accidentally get transmitted to the application server.

ASP.NET Web Forms

Almost as a routine, assessments of commerce systems written in ASP.NET Web Forms present this issue, especially if tokenization was not in its original feature set. Web Forms keeps the developer abstracted a layer or two away from the bare metal of the web (which is why Microsoft created ASP.NET MVC), and the use of “controls” to promote code reuse can result in forgotten or unexpected behavior. Requests from web control validators, partial update panels and other AJAX-ish ASP.NET controls can generate events that require round-trips to the server, like this request, for example, which I captured from an actual production commerce system (and sanitized):

POST /CreditCardPayment.aspx?c29tZXBhcnRpYWx1cGRhdGVzdHJpbmdnb2VzaGVyZS1rdWRvc3RveW91Zm9yZGVjb2Rpbmd0aGlzIQ== HTTP/1.1
Host: somefakeretailer.com:443
Connection: keep-alive
Content-Length: 3265
X-Requested-With: XMLHttpRequest
Cache-Control: no-cache
X-MicrosoftAjax: Delta=true
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.91 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept: */*
[…snip…]
 
ScriptManager1=upCcNumber&txtboxfname=Tim&cardNumber=4111111111111111&securityCodeNumber=123&txtboxlname=MalcomVetter&ddlExpMM=01&ddlExpYYYY=2017[…snip…]

In the above example, the developers did not realize it was happening, and the application server was not persisting the credit card numbers, but the numbers were transmitted to the application server, which technically ruins the point of tokenization. (It also exposes the application to additional attacks, as we will discuss later.) The solution is not to mix any controls that make __doPostBack() calls on the same pages that present payment forms. In the .NET developer community, there are humorous T-shirts that sum this up quite well. Here is one example:

Some clever developers attempted to mix and match these types of controls on payment forms by inserting their own JavaScript to purge the values of the credit card numbers on the post back events before data is sent to the server. Anyone who has spent any time with ASP.NET Web Forms, nesting complicated controls, knows firsthand that DOM IDs for these fields can be very complicated:

ctl00_ContentPlaceHolder1_TextBox3

These IDs can be changed with any upstream code checked in by another developer on the same team later. As a result, this type of approach is like walking on eggshells made out of JavaScript, especially if your QA team does not specifically check for this JavaScript sanitization before each release. 

Card Type Prefix Fetching

Another common example of careless transmission of credit card data in web apps is the predetermination of card types on payment detail forms. For example, we observed the following request taking the onkeypress events from a credit card text field and sending them in JSON to the server so that the card type drop down list box could be dynamically filled in:

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

{"ccPrefix":"41111111"}

The first key press event generated a post similar to the above with a "ccPrefix" of "4" followed by "41" then "411" and so on. It was immediately apparent that the JavaScript logic was flawed and would result in the first 15 digits of the full 16-digit number (or close to it depending on if the user typed faster than the JavaScript could execute its next pass) being sent to the application server. The developers probably thought they were saving their customer a couple clicks on the card type drop down box, but would have been better served leaving the card type drop down off the payment form entirely, instead implementing the card type lookup logic on the server side. In general, latching onto events like onkeypress generates behavior that is slightly different per browser or even browser add-ons. For example, a saved credit card number that is automatically pasted into the form by a browser’s credit card e-wallet may not even trigger the onkeypress event at all.

Form Tags and Other JavaScript Bugs

Another tokenization implementation flaw that can be found during deep scrutiny is a set of <form> tags with an “action” that is intended to be modified to by JavaScript at runtime in the browser to point to the tokenization server. This is similar to the ASP.NET web controls issue above but is in applications built on many development platforms, like Java, PHP, etc. To do tokenization correctly (as stated numerous times above, but here it comes once more), the client-side has to behave as expected. It must send the credit card details to the tokenization service and not to the main commerce application server for any reason. 

Since the browser must behave, the JavaScript powering the browser must behave, and all the platform-specific behavioral nuances must be considered completely by the developers. If a customer’s browser generates an exception in the JavaScript which causes the logic to fall out of the function that switches the <form> tag’s action, then that customer’s browser will be sending PANs to the application server, like it or not. Even more dangerous is linking to hosted JavaScript libraries on different domains, since a Man-In-The-Middle or even a timeout to fetch those libraries can cause unanticipated logic flaws.

Scraping the RAM of Web Servers

It’s common knowledge that attackers breach point-of-sale (POS) systems by scraping RAM for credit card data, searching for regular expressions that match PANs and even full magnetic stripe data as a post-exploitation activity (after attackers gain an initial foothold into the systems). This is possible because of the architecture—the POS systems have to have a copy of PANs in memory for at least a brief amount of time. It’s not often remembered, but the same is true for any web server that receives a PAN as a web request—even if that request was submitted over HTTPS, encoded as JSON, form-urlencoded, XML, etc., the server will have a plaintext copy in RAM temporarily—ripe for RAM scraping.

With a foothold into the servers hosting the commerce application, an attacker can scrape RAM to harvest credit card data that was sent to the server, whether sent erroneously or not. If the application is built in Java or .NET (or other languages with immutable strings) the persistence of these little credit card gems in memory increases considerably and would not require repeated real-time scraping. On a high-volume commerce app, if even 5-10 percent of the transactions erroneously transmitted credit card data back to the main app servers, there would be more than sufficient economic incentive for an attacker to hang out and pick up a copy of this low-hanging fruit. 

Was It a DevOps Accident?

As seen in this post, even a subtle flaw could result in the accidental transmission of credit card data back to the commerce server, which could end up in debug logs, core dumps, or just scraped from RAM.  With DevOps, organizations now have much more code savvy individuals supporting their continuous delivery deployments.  With JavaScript heavy apps, a full build (compilation) of the application is not necessary to introduce unwanted changes to the application’s logic. It is common for merchants to have layers of controls around the build automation—who introduced what code changed and when, but once that code is released to the DevOps team to maintain in the production environment, what file integrity controls are monitoring for changes in the JavaScript?  From my experience as a developer and a consultant, rarely do organizations watch the content of .js files.  Introducing and reverting a malicious change in JavaScript by a developer-minded admin gone rogue would be easy to accomplish with just a simple text editor on the server (think: means, motive, opportunity). 

Exfiltration of PANs by a malicious member of a DevOps team is also plausible. “I saw some performance issues, so I am profiling some processes, which may generate some process dumps or debug logs on the commerce app servers.”  If ever there was a possibility for a malicious insider to pull off a “salami slicing” attack, this is it. Unfortunately, penetration tests where the tester gets to pretend to be a build and release engineer are very rare.

Recap

Commerce applications that integrate with credit card tokenization systems, especially enterprise-specific systems, are not automatically immune from attacks, no matter what your PCI QSA or maybe even your payment gateway service provider says. Like anything else, the devil is in the details, so resist the binary response of, “We do tokenization, therefore, we are safe.” Mature organizations that care about managing risk will go beyond the PCI minimum required testing and bring in experts that understand these attack vectors, and carefully analyze their payment systems with a fine-tooth comb.

Stay tuned as we continue this series with some case studies of exploiting credit card tokens during penetration tests of our clients’ networks.