← Back to all reports

CVV Stored in Cleartext via GraphQL API - PCI DSS Violation

Reported Mar 26, 2026
Severity High
Platform Android
Vulnerability Class Sensitive Data Exposure (CWE-312)
Target Type Crypto Wallet / Card Platform
Impact Full card data exposure, PCI DSS non-compliance

The Risk

The app stored customers' full credit card numbers and CVV security codes in its own database and returned them in plaintext through its API. This is the equivalent of a shop writing down every customer's card details in a notebook and leaving it on the counter. Any breach of the backend would expose complete, ready-to-use card data for every user who saved a card. This violates the most fundamental rule in payment card security: never store the CVV.

The Vulnerability

Static analysis of the Android app (JADX decompilation and React Native bundle analysis) revealed a complete absence of any payment tokenization. No payment SDK was present: no Stripe, no Braintree, no Adyen, no processor SDK of any kind. Raw card data flowed directly from the app to the company's own backend.

Three confirmed issues

1. CVV persisted and returned in cleartext

The saveCard GraphQL mutation accepted cardCvv as a string parameter and stored it in the database. The CurrentCards query returned the CVV alongside the full unmasked card number. Only a session bearer token was required. No PIN, no 2FA, no re-authentication.

2. Card data round-tripped through injectable JavaScript

A getInjectScript query accepted raw PAN, CVV, and expiry as GraphQL variables. The server processed these and returned a JavaScript snippet with the card credentials embedded as string literals, designed to auto-fill forms on four third-party payment gateways.

3. No tokenization architecture exists

The HTTP client used a bare constructor with no certificate pinning and cleartext traffic enabled in the manifest. The entire payment flow bypassed industry-standard tokenization. The backend stored, processed, and transmitted raw cardholder data instead of delegating to a PCI-compliant processor.

The Attack

The proof of concept demonstrated the full round-trip using the researcher's own authenticated session:

  1. Save a card via the saveCard mutation, providing a full card number, expiry, and CVV
  2. Query CurrentCards. The API returns all saved cards with full PAN and CVV in cleartext
  3. Call getInjectScript with the same card data. The server returns a JavaScript block containing the credentials as embedded strings
  4. Inspect the returned JavaScript. It contains auto-fill logic for four different payment gateway domains with the raw card data

The vulnerability doesn't require exploiting another user's account. The issue is that CVV data exists in storage at all. PCI DSS Requirement 3.2 explicitly prohibits storing CVV/CVC2 data after authorization, under any circumstances, even if encrypted.

The Impact

PCI DSS violations

Requirement 3.2: CVV must never be stored post-authorization. Requirement 3.4: PAN must be rendered unreadable in storage. Requirement 4.1: Cardholder data must be encrypted in transit (cleartext traffic was enabled). Any single one of these would be a compliance failure.

Mass card exposure risk

Any backend compromise, SQL injection, or insider threat would expose complete, ready-to-use card data (PAN + CVV + expiry) for every user who saved a card. No cracking or decryption required.

Card fraud enablement via JavaScript injection

The getInjectScript flow created a server-side template injection surface. If the auto-fill JavaScript was intercepted or the gateway domains were spoofed, card data could be silently redirected.

Remediation

  • Immediately stop storing CVV data. Delete all CVV values from the database
  • Integrate a PCI-compliant payment processor (Stripe, Braintree, Adyen) and use their tokenization SDKs
  • Replace the saveCard flow with processor-issued card tokens. Never send raw card data to your own backend
  • Remove the getInjectScript endpoint entirely. Card data should never be embedded in server-generated JavaScript
  • Enable certificate pinning and disable cleartext traffic in the Android manifest
  • Engage a Qualified Security Assessor (QSA) for PCI DSS compliance review