Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Payments
DescriptionInappropriate implementation in Payments
ComponentPayments
Bug ClassLogic Error
Tracker501738451
Fix commitcf568b257f76 (chromium/src) +2/-2
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Files Changed

  • components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestVerifier.java
From cf568b257f76286d5475c4c0d9c4ab0d73759aff Mon Sep 17 00:00:00 2001
From: Andrew Paseltiner <apaseltiner@chromium.org>
Date: Wed, 15 Apr 2026 06:16:10 -0700
Subject: [PATCH] Use GURL.getSpec() for payment manifest cache key

In release builds, GURL.toString() falls back to Object.toString(),
which incorporates the object's hash code. Because GURL overrides
hashCode() with a deterministic value (the hash of the URL string), the
toString() output was stable across executions. This allowed it to
function as a persistent cache key, but also made it susceptible to
32-bit hash collisions.

An attacker could pre-compute a URL with a colliding hash to poison the
manifest cache, bypassing verification for legitimate payment methods.

Before this change, keys were persisted in a format like
'org.chromium.url.GURL@7f3a2b1c'. After this change, the full URL string
(e.g., 'https://bobpay.com/pay') is used instead.

This CL fixes the issue by using GURL.getSpec() consistently for cache
interactions in PaymentManifestVerifier.java. Switching to the full URL
string will cause a one-time invalidation of existing cache entries,
which will be re-verified and re-cached automatically.

Fixed: 501738451
Change-Id: Ifa16f171d0408602c9b9e86b055950b7f8b14266
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7756260
Commit-Queue: Andrew Paseltiner <apaseltiner@chromium.org>
Reviewed-by: Stephen McGruer <smcgruer@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1615095}
---

diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestVerifier.java b/components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestVerifier.java
index a2c81602..ced3779 100644
--- a/components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestVerifier.java
+++ b/components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestVerifier.java
@@ -264,7 +264,7 @@
         }
 
         // Try to fetch manifest from the cache first.
-        if (!mCache.getPaymentMethodManifest(mMethodName.toString(), this)) {
+        if (!mCache.getPaymentMethodManifest(mMethodName.getSpec(), this)) {
             mIsManifestCacheStaleOrUnusable = true;
             mDownloader.downloadPaymentMethodManifest(mMerchantOrigin, mMethodName, this);
         }
@@ -455,7 +455,7 @@
 
         // Cache supported apps' package names and origins. (Also cache "*" if applicable.)
         mCache.addPaymentMethodManifest(
-                mMethodName.toString(),
+                mMethodName.getSpec(),
                 mAppIdentifiersToCache.toArray(new String[mAppIdentifiersToCache.size()]));
 
         // Cache supported apps' parsed manifests.
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential cache poisoning in PaymentManifestVerifier via GURL.toString() hash collision

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the Chrome Security team.

Overview: In release builds, PaymentManifestVerifier uses GURL.toString() as a cache key, which falls back to Object.toString() and outputs a string containing the URL’s 32-bit Java hash code. An attacker can exploit this easily computable hash collision to poison the payment manifest cache. This potentially allows a malicious Android app to bypass verification and intercept payment data for legitimate, unassociated payment methods.

Affected files:

  • components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestVerifier.java
  • url/android/java/src/org/chromium/url/GURL.java
  • components/payments/content/android/java/src/org/chromium/components/payments/AndroidPaymentAppFinder.java
  • components/payments/content/web_payments_table.cc
  • components/payments/content/android/web_payments_web_data_service_android.cc

Estimated timestamp from git blame: 2017-08-01

The PaymentManifestVerifier in Chrome for Android utilizes a persistent SQLite-backed cache (via WebPaymentsWebDataService) to store validated payment method manifests. This cache prevents Chrome from needing to download and parse manifest files every time a payment request is initiated.

The Vulnerability

There is a flaw in how the cache key is generated for payment method manifests in PaymentManifestVerifier.java. Both when reading from the cache (line 267) and when writing to the cache (line 458), the verifier uses mMethodName.toString() as the cache key, where mMethodName is a GURL object.

In official Android release builds (where BuildConfig.ENABLE_ASSERTS is false), org.chromium.url.GURL.toString() is implemented to return super.toString(). Since GURL does not explicitly extend another class, this resolves to java.lang.Object.toString(), which returns getClass().getName() + "@" + Integer.toHexString(hashCode()).

Because GURL overrides hashCode() to return the 32-bit Java String.hashCode() of the URL spec, the resulting cache key string is org.chromium.url.GURL@ followed by the hex representation of the URL’s 32-bit string hash.

Java’s String.hashCode() algorithm uses a small multiplier (31) and a 32-bit space, making it highly susceptible to trivial preimage attacks (hash collisions). This means an attacker can easily find an arbitrary URL whose hash matches the hash of a target legitimate payment method URL.

Potential Attack Scenario

(Note: These are suggested steps; our tooling agent cannot execute code to verify this end-to-end.)

  1. Preconditions: An attacker computes an attacker_url (e.g., https://attacker.com/collision) that hash-collides with a target payment method (e.g., https://bank.com/pay). The attacker induces a user to install two malicious apps: App A (registered for attacker_url) and App B (registered for target_url and designed to steal payment data).
  2. Cache Poisoning: The user visits an attacker-controlled HTTPS page that initiates a PaymentRequest for the attacker_url. Chrome creates a verifier, downloads the attacker’s manifest, and validates App B as a related application. Crucially, the verifier writes this verification result to the SQLite cache using mMethodName.toString() as the key. Because of the hash collision, this poisoned key is identical to the key that will be used for the target URL.
  3. Exploitation: When the user later visits a legitimate merchant that requests the target_url payment method, PaymentManifestVerifier queries the cache using target_url.toString(). Due to the collision, it retrieves the attacker’s poisoned entry. The verifier then incorrectly validates the attacker’s App B against the poisoned manifest cache, authorizing it as a legitimate handler for the target payment method.

Impact

An attacker can successfully bypass the W3C Payment Method Manifest verification mechanism. This allows a malicious application to be presented as a valid payment handler on a legitimate merchant’s site, enabling the app to receive the merchant’s payment intent, including sensitive cross-origin data such as the topLevelOrigin, paymentRequestOrigin, and the merchant’s specific PaymentMethodData.

Suggested Fix

In PaymentManifestVerifier.java, modify all cache interactions to use the actual URL string rather than toString().

Change lines 267 and 458 to use mMethodName.getSpec() instead of mMethodName.toString().

For example, line 267 should be: if (!mCache.getPaymentMethodManifest(mMethodName.getSpec(), this)) {

And line 457-458 should be: mCache.addPaymentMethodManifest(mMethodName.getSpec(), ...)

This ensures the exact URL string is used as the unique key in the SQLite database, preventing hash collision attacks.

Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker