The PaymentRequest.canMakePayment()
method allows a website to silently query the 'availability' of payment apps. From the spec:
For each paymentMethod tuple in request.[[serializedMethodData]]:
Let identifier be the first element in the paymentMethod tuple.
If the user agent has a payment handler that supports handling payment
requests for identifier, resolve hasHandlerPromise with true and
terminate this algorithm.
If an attacker is able to install multiple payment apps, they can encode a UUID as a set of installed payment apps (each for a different payment method, e.g., https://evil.example/1, https://evil.example/4, …). A colluding website can then later construct a Payment Request for, and call canMakePayment() on each app in turn. By checking whether the method returns true or false, the website can build up the original UUID and thus allow tracking.
Proposed Mitigation
The feasibility of this attack very much depends on the cost to install a payment app, as it requires installing enough bits of entropy to track users. The removal of PaymentInstruments.set() went a long way to mitigating it, as it allowed for silent installation. JIT-installed payment apps are much 'louder', as it requires a user-activation per show() call, as well as user-visible interaction (see issue 416), so it may be OK to not act in that case.
Further mitigations here could involve some sort of 'trust' model around payment apps, but it has not been explored significantly.
Android Variant
Not technically in scope for Web Payment APIs, but sharing for transparency --- there is a variant of the above attack where a single Android application lists itself as able to handle (say) 32 different payment methods (https://evil.example/1, https://evil.example/2, …). A website can then query those payment methods in order via canMakePayment(), and the Android app can respond with true/false to build up the UUID.
To further mitigate this, we are likely to restrict the number of payment methods that a single Android application can claim to handle.
(Below I quote previous text from the Chrome team, moving it from this pull request [1] to an issue) [1] https://github.com/w3c/webpayments/pull/261
The
PaymentRequest.canMakePayment()
method allows a website to silently query the 'availability' of payment apps. From the spec:If an attacker is able to install multiple payment apps, they can encode a UUID as a set of installed payment apps (each for a different payment method, e.g., https://evil.example/1, https://evil.example/4, …). A colluding website can then later construct a Payment Request for, and call
canMakePayment()
on each app in turn. By checking whether the method returnstrue
orfalse
, the website can build up the original UUID and thus allow tracking.Proposed Mitigation
The feasibility of this attack very much depends on the cost to install a payment app, as it requires installing enough bits of entropy to track users. The removal of
PaymentInstruments.set()
went a long way to mitigating it, as it allowed for silent installation. JIT-installed payment apps are much 'louder', as it requires a user-activation pershow()
call, as well as user-visible interaction (see issue 416), so it may be OK to not act in that case.Further mitigations here could involve some sort of 'trust' model around payment apps, but it has not been explored significantly.
Android Variant
Not technically in scope for Web Payment APIs, but sharing for transparency --- there is a variant of the above attack where a single Android application lists itself as able to handle (say) 32 different payment methods (https://evil.example/1, https://evil.example/2, …). A website can then query those payment methods in order via
canMakePayment()
, and the Android app can respond withtrue
/false
to build up the UUID.To further mitigate this, we are likely to restrict the number of payment methods that a single Android application can claim to handle.