← Back to all reports

Wallet Deep Link Loads Arbitrary URL in Privileged dApp Browser, Address and Signatures Captured

Reported Feb 27, 2026
Severity High
Platform Android
Vulnerability Class Deep Link Injection (CWE-939)
Target Type Layer 1 Blockchain Wallet
Impact Wallet address and signature capture, gasless token drain

The Risk

A user could be sent a special link that opens the wallet app, loads an attacker page inside the wallet's in-app browser, and immediately asks them to sign what looks like a routine verification message. One tap on the link, one tap on Sign, and the attacker walks away with the user's wallet address and a cryptographic signature that can be used to drain their tokens. The attacker page lives inside the official wallet's own browser, with the wallet's branding visible on screen, so victims have no obvious way to spot the trap.

The Vulnerability

Unvalidated URL parameter in deep link handler

The wallet registered a custom URL scheme that routed to its in-app dApp browser. The handler took a url query parameter and passed it directly into the WebView with no allowlist, no scheme check, and no user confirmation. The Java entry point looked like:

String url = data.getQueryParameter("url");   // attacker-controlled
if (action.equals("web")) {
    Z(url);                                    // passed directly to WebView
}

void Z(String url) {
    Intent intent = new Intent(this, XAppsActivity.class);
    intent.putExtra("BROWSER_URL", url);       // no allowlist, no scheme check
    startActivity(intent);
}

No App Link verification on the scheme

The intent filter for the deep link was exported with the BROWSABLE category and no android:autoVerify, so any browser, app, or email could fire it without the operating system checking domain ownership.

Signing bridge injected into every page

The dApp browser injected a JavaScript signing bridge into every loaded page with no origin check. Through that bridge, page JavaScript could call:

Bridge methodWhat it does
requestAccountsReturns the victim's wallet address silently with no dialog
signPersonalMessageTriggers a signing dialog for an attacker-chosen message
signTypedDataV4Triggers a typed data signing dialog usable for gasless token approvals
signTransactionSigns an arbitrary transaction for direct fund transfer
wallet_addEthereumChainAdds an attacker-controlled RPC endpoint to the wallet

The Attack

  1. The attacker hosts a delivery page on any HTTPS site, with a button that fires the wallet's deep link with the attacker URL as the url parameter.
  2. The victim taps the button. The wallet app opens and loads the attacker page inside the dApp browser. The toolbar shows the wallet's lock icon, making the page appear trusted.
  3. The attacker's JavaScript calls requestAccounts, which silently returns the wallet address with no prompt.
  4. The page calls signPersonalMessage with text crafted to look like a standard verification: "Please sign this message to verify your wallet ownership. This will not trigger a transaction or cost any gas fees."
  5. The native signing dialog appears. The victim taps Sign.
  6. The signature is exfiltrated to the attacker's server.

Escalation to fund drain

Swapping signPersonalMessage for signTypedDataV4 with an EIP-2612 Permit payload lets the attacker capture an off-chain approval that authorizes a token transfer. A single follow-up on-chain call by the attacker then drains the victim's token balance with no further victim interaction. Replacing it with signTransaction moves funds directly. The same one-tap path supports either escalation.

Confirmed capture

The proof of concept captured a real wallet address and a 65-byte ECDSA signature from a test device. The signature was independently verified to recover the captured wallet address, proving end-to-end exploitation, not just bridge access.

The Impact

Any user of the wallet who tapped a single attacker-controlled link could have their wallet address harvested silently and a cryptographically valid signature captured with one further tap on a familiar-looking dialog. The same code path supports gasless token drain via Permit signing or direct transaction submission, both of which are irreversible once on chain.

The attack scales: one attacker domain, one link distributed in any chat, social media post, or airdrop announcement, every recipient with the wallet installed at risk.

Remediation

  • Validate the url parameter in the deep link handler. Accept only HTTPS URLs against an allowlist of legitimate dApp domains.
  • Reject javascript: and data: schemes before passing any URL into the WebView.
  • Show a confirmation dialog before opening any page from a deep link, displaying the full domain.
  • Add android:autoVerify="true" to the deep link intent filter so the operating system enforces domain ownership.
  • Add an origin check in the JavaScript bridge so signing methods only work for verified dApp origins.