harden: escape every interpolation into popup innerHTML, and add default-src to both manifests (closes #307)
A hostile ERC-20's symbol() reached an innerHTML string unescaped, and neither manifest declared default-src, so an attacker deploying a token with 1,000+ holders and airdropping one unit could render a full-viewport cross-origin iframe over the wallet's own UI, on screens where the user types their password. escapeHtml is now a pure string replace over & < > " ' — the old version round-tripped through textContent, which escapes neither quote, while already being used inside data-copy="...". All 19 files in src/popup/views/ were audited: beyond the reported symbol site, the explorer-supplied directionLabel in all three transaction lists, wallet.name, addr.ensName, the blockie data: URIs and two ad-hoc quote-only escapes were also unescaped. Explorer URLs now go through one helper that percent-encodes the path segment. Both manifests add default-src 'self', frame-src 'none', form-action 'none' and base-uri 'none'. Three loosenings are pinned in tests/manifest.test.js and justified in README.md: style-src 'unsafe-inline' (39 static style attributes; Firefox implements neither style-src-attr nor 'unsafe-hashes'), img-src data: (blockies), connect-src https: http: (user-configurable RPC). Note frame-src 'none' blocks a frame loading, not the element existing, so the zero-iframe assertion is a claim about the escaping alone; the test asserts the element count and the literal rendered text separately, taking the count before any click an overlay could intercept. Verified: make check 39 suites / 811 tests, test-e2e 55/55 including the WebAssembly-under-CSP assertion, test-e2e-firefox 8/8, zero CSP violations asserted rather than merely unobserved. Reverting only balanceLine's interpolation reproduces the attack as 2 iframes on the address screen.
This commit was merged in pull request #327.
This commit is contained in:
@@ -9,6 +9,7 @@ const {
|
||||
addressDotHtml,
|
||||
addressTitle,
|
||||
escapeHtml,
|
||||
displaySymbol,
|
||||
truncateMiddle,
|
||||
balanceLine,
|
||||
renderAddressHtml,
|
||||
@@ -124,7 +125,11 @@ function show() {
|
||||
currentSymbol = symbol;
|
||||
|
||||
$("address-token-title").textContent =
|
||||
wallet.name + " \u2014 Address " + (ai + 1) + " \u2014 " + symbol;
|
||||
wallet.name +
|
||||
" \u2014 Address " +
|
||||
(ai + 1) +
|
||||
" \u2014 " +
|
||||
displaySymbol(symbol);
|
||||
|
||||
// Blockie
|
||||
const blockieEl = $("address-token-jazzicon");
|
||||
@@ -174,7 +179,9 @@ function show() {
|
||||
(knownToken && knownToken.symbol) ||
|
||||
null;
|
||||
const tokenName = rawName ? escapeHtml(rawName) : null;
|
||||
const tokenSymbol = rawSymbol ? escapeHtml(rawSymbol) : null;
|
||||
const tokenSymbol = rawSymbol
|
||||
? escapeHtml(displaySymbol(rawSymbol))
|
||||
: null;
|
||||
const tokenDecimals =
|
||||
tb && tb.decimals != null
|
||||
? tb.decimals
|
||||
@@ -288,10 +295,12 @@ function renderTransactions(txs) {
|
||||
const counterparty = tx.direction === "sent" ? tx.to : tx.from;
|
||||
const ensName = ensNameMap.get(counterparty) || null;
|
||||
const title = addressTitle(counterparty, state.wallets);
|
||||
const dirLabel = tx.directionLabel;
|
||||
// The explorer's method name for a contract call, title-cased.
|
||||
const dirLabel = escapeHtml(tx.directionLabel);
|
||||
const sym = displaySymbol(tx.symbol);
|
||||
const amountStr = tx.value
|
||||
? escapeHtml(tx.value + " " + tx.symbol)
|
||||
: escapeHtml(tx.symbol);
|
||||
? escapeHtml(tx.value + " " + sym)
|
||||
: escapeHtml(sym);
|
||||
const maxAddr = Math.max(32, 36 - Math.max(0, amountStr.length - 10));
|
||||
const displayAddr =
|
||||
title || ensName || truncateMiddle(counterparty, maxAddr);
|
||||
@@ -361,7 +370,7 @@ function init(_ctx) {
|
||||
}
|
||||
// Hide dropdown, show static token display
|
||||
$("send-token").classList.add("hidden");
|
||||
let staticHtml = `<div class="font-bold">${escapeHtml(currentSymbol)}</div>`;
|
||||
let staticHtml = `<div class="font-bold">${escapeHtml(displaySymbol(currentSymbol))}</div>`;
|
||||
if (tokenId !== "ETH") {
|
||||
staticHtml += `<div class="text-xs">${renderAddressHtml(tokenId)}</div>`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user