Simple payment standard will be the new cool thing web browsers can do. This happens thanks to W3C Payment Request API. Early on it has been even featured in the New York Times and rightly so, as it has a big potential. Why?
Chances are that the days of e-commerce sites having custom, often unintuitive payment schemes will go away. Payment Request API improves not only convenience, but also security of online payments. Web sites will have it easier to ship payment options. Web users will have it easier to avoid phishing, scams and other unpleasant experience.
Web Payment API works as follows:
- payment process is initiated by the site
- browser takes control; the user either can provide payment method such as a credit card, then a CVV number, and that’s it.
All using a standard, intuitive, browser-supported display messages. Payment Request API is now supported in Chrome and Edge, and will soon ship to Firefox and WebKit.
A lot of thought and resources went into the development of Payment Request API, including considering its security and privacy aspects. Still, it’s often difficult to design things considering all possible misuse scenarios. In this post, I’m including just a small addition. I’m not providing a comprehensive privacy review of the specification. My focus is put on a particular and possibly most troubling aspect
This aspect enables:
- In general, fingerprinting and possibly profiling, and...
- In Chrome browser - detecting incognito (a.k.a. private browsing mode) mode reliably, a thing that generally should not be possible
I believe both issues might have their origin in the specification. I include a discussion of the specification changes in the appendix.
Web Request API
Users have an option to add a credit card to a browser, so it’s later simple to pay using a previously-included credit card. To make web developers and user’s life easier, the specification includes a method of checking if the user has a saved payment instrument. This method is called canMakePayment and that’s the main character starring in this privacy review.
To protect from abuses, canMakePayment can be executed by an origin (i.e. a website) with a specified target payment instrument only once in a while. So say, a website can test whether a browser has a Visa card included - but checking (immediately afterwards) if Mastercard card is also supported - should be impossible. This mechanism protects from enumerating payment instruments included in a browser. For example, that’s how it’s implemented in Chrome. Specific origin (i.e. website) can make such a test each 30 minutes only, via a quota mechanism. If a website attempts to make the test more frequently, Chrome won’t allow. The quota mechanism makes enumeration/fingerprinting attacks more difficult - as in, needing more time.
However, a website could simply use a bunch of iframes with scripts effectively running in different origins, meaning that the 30m quota is functionally irrelevant (it’s a type of utility vs privacy bargain from the beginning, anyway). Such iframes (using the fingerprinting-friendly setting of “allowpaymentrequest”) would then perform additional quota-less tests. For example, one iframe could test for “visa”, another for “mastercard”, etc. At the end, iframes communicate test results to the parent frame. Ultimately, this process would enable enumerating all payment instruments supported by the user.
Enumerating saved cards
Prior to running the demo, you need a visa or mastercard card saved in your Chrome. Feel free to use my Visa: 4916 6293 0528 7782 (CVV 933). You can try it here.
Then you can have a look at the simple fingerprinting demonstration proof of concept here. I tested it on Chrome, desktop and Android. In this demo, only two cards are tested: visa and mastercard.
That’s not all.
Payment Request API as shipped in Chrome allows detection when a user is browsing in incognito mode. It’s as simple as testing two different payment instruments by the same website. If I make a request to canMakePayment with “visa”, then again with “mastercard” - both from the same website - the second method call will result in an exception when not in Incognito:
NotAllowedError: Not allowed to check whether can make payment”.
However, in Incognito mode the result (in this example I only have a Visa included) is:
mastercard card enabled? true
visa card enabled? true
Detecting Incognito Mode
I’ve set up a demo of Chrome Incognito detection here.
The demonstration works on Chrome (desktop; 61.0.3163.100). Under Android things work a bit differently - queue limits are enforced. But... It’s possible to reuse the fingerprinting scheme described above to effectively detect incognito mode anyway. That’s because each iframe returns “true”. How likely is it that a visitor has all cards set in the browser? All, meaning: visa, mastercard, jcb, amex, diner, and so on.
Ultimately, with this particular issue, we have also turned a fingerprinting vector into an information leak! Not bad.
- Wait for an update (I reported this item to Chrome, Issue 766973), or
- Disable Web Payments in Chrome flags
How to solve it
There are a number of improvements one could imagine.
- Allow execution of canMakePayment only in top-level browsing context (as in the spec) (implementation issue)
- Make the requests visible to the user, and auditable (API spec and user interface issue)
- Make the user opt-in to canMakePayment, so checking whether he has added a credit card would be with his consent (API spec, implementation)
- Remove canMakePayment (API spec issue) if concerned, as some level of leaks will always be inevitable.
Moreover, the W3C specification could benefit from clear and unambiguous definitions. As we have seen, the current drafting of how canMakePayment apparently allows for implementation relaxation that may (did) result in inconsistent behavior, and issues like the ones discussed in this post. It should be made clear what happens when canMakePayment finds a match (currently not exactly the case).
It’s a nice place for normative language, such as (something) “MUST” (work this or that way). Following my Chrome bug submission, specification might get a small refinement.
Privacy reviews often provide for fascinating opportunities of spotting issues in places where one does not expect finding them. I had reservations on canMakePayment from its beginning. Regrettably I did not find the time/resources to pursue the issue (nor am I involved in Web Request API spec) back then. Coincidentally, I have found the motivation now. The filled Chrome issue now looks (partly) addressed. It appears that the W3C specification will be amended accordingly as well.
Ultimately, the issue provided another interesting input about designing with privacy and privacy engineering. Some mechanisms are sensitive enough to require a broad privacy review. As I often say, these kind of audits benefit from an out-of-the-box approach.
Consider supporting analysis of the kind (btc 1HzvUk6RsLRkdFG7aRq7xwtLRgrKP2sqBY).
Appendix: where did we get canMakePayment?
Last question worth answering is the provenance of canMakePayment. In other words, how did we get it in the first place?
I learned that canMakePayment has apparently been inspired by Apple Pay mechanism canMakePaymentsWithActiveCard, allowing to test if Apple Pay is active. I did not check whether Apple Pay method has similar issues as above, although that is obviously interesting. Furthermore, believe that canMakePayment-like functionality as defined in the W3C standard could be something welcomed by iOS/Apple ecosystem as the operational analogies are obvious.
However, it’s important to note that transferring this method in a straightforward fashion into the W3C spec may not be so obvious. In the Apple Pay case, we’re speaking about 1 bit only (Apple Pay off or on), in the general case of a web browser (as in the W3C spec), more payment methods are available.
Method canMakePayment has been included in the W3C spec with this commit and its operation is described here. The motivation appears to be that merchants simply wanted an easy way of detection (“We’ve heard very strong feedback from merchants”). At that point, it is clear the motivation is legitimate, what may be a bit less clear is who is “we”, or which “merchants” specifically - though this may not be so important, of course.
It is also the place where the 30 minute quota implemented in Chrome is mentioned, too.