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.
77 lines
2.4 KiB
JavaScript
77 lines
2.4 KiB
JavaScript
const {
|
|
$,
|
|
showView,
|
|
showFlash,
|
|
flashCopyFeedback,
|
|
formatAddressHtml,
|
|
addressTitle,
|
|
displaySymbol,
|
|
attachCopyHandlers,
|
|
goBack,
|
|
} = require("./helpers");
|
|
const { state, currentAddress, currentNetwork } = require("../../shared/state");
|
|
const QRCode = require("qrcode");
|
|
|
|
function show() {
|
|
const addr = currentAddress();
|
|
const address = addr ? addr.address : "";
|
|
const title = address ? addressTitle(address, state.wallets) : null;
|
|
const ensName = addr ? addr.ensName || null : null;
|
|
$("receive-address-block").innerHTML = address
|
|
? formatAddressHtml(address, ensName, null, title)
|
|
: "";
|
|
$("receive-address-block").dataset.full = address;
|
|
// Etherscan link is now included in formatAddressHtml via renderAddressHtml
|
|
$("receive-etherscan-link").innerHTML = "";
|
|
if (address) {
|
|
QRCode.toCanvas($("receive-qr"), address, {
|
|
width: 200,
|
|
margin: 2,
|
|
color: { dark: "#000000", light: "#ffffff" },
|
|
});
|
|
}
|
|
const warningEl = $("receive-erc20-warning");
|
|
if (state.selectedToken && state.selectedToken !== "ETH") {
|
|
// Look up symbol from address token balances
|
|
const addrObj = currentAddress();
|
|
let symbol = state.selectedToken;
|
|
if (addrObj) {
|
|
const tb = (addrObj.tokenBalances || []).find(
|
|
(t) =>
|
|
t.address.toLowerCase() ===
|
|
state.selectedToken.toLowerCase(),
|
|
);
|
|
if (tb) symbol = tb.symbol;
|
|
}
|
|
warningEl.textContent =
|
|
"This is an ERC-20 token. Only send " +
|
|
displaySymbol(symbol) +
|
|
" on " +
|
|
currentNetwork().name +
|
|
" to this address. Sending tokens on other networks will result in permanent loss.";
|
|
warningEl.style.visibility = "visible";
|
|
} else {
|
|
warningEl.textContent = "";
|
|
warningEl.style.visibility = "hidden";
|
|
}
|
|
showView("receive");
|
|
attachCopyHandlers("view-receive");
|
|
}
|
|
|
|
function init(_ctx) {
|
|
$("btn-receive-copy").addEventListener("click", () => {
|
|
const addr = $("receive-address-block").dataset.full;
|
|
if (addr) {
|
|
navigator.clipboard.writeText(addr);
|
|
showFlash("Copied!");
|
|
flashCopyFeedback($("receive-address-block"));
|
|
}
|
|
});
|
|
|
|
$("btn-receive-back").addEventListener("click", () => {
|
|
goBack();
|
|
});
|
|
}
|
|
|
|
module.exports = { init, show };
|