An externally hosted phishing warning page is now used rather than the built-in phishing warning page.The phishing page warning URL is set via configuration file or environment variable. The default URL is either the expected production URL or `http://localhost:9999/` for e2e testing environments. The new external phishing page includes a design change when it is loaded within an iframe. In that case it now shows a condensed message, and prompts the user to open the full warning page in a new tab to see more details or bypass the warning. This is to prevent a clickjacking attack from safelisting a site without user consent. The new external phishing page also includes a simple caching service worker to ensure it continues to work offline (or if our hosting goes offline), as long as the user has successfully loaded the page at least once. We also load the page temporarily during the extension startup process to trigger the service worker installation. The old phishing page and all related lines have been removed. The property `web_accessible_resources` has also been removed from the manifest. The only entry apart from the phishing page was `inpage.js`, and we don't need that to be web accessible anymore because we inject the script inline into each page rather than loading the file directly. New e2e tests have been added to cover more phishing warning page functionality, including the "safelist" action and the "iframe" case.feature/default_network_editable
parent
8a14504b63
commit
7199d9c567
@ -1,150 +0,0 @@ |
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<title>MetaMask Phishing Detection</title> |
||||
<script |
||||
src="./globalthis.js" |
||||
type="text/javascript" |
||||
charset="utf-8" |
||||
></script> |
||||
<script |
||||
src="./lockdown-install.js" |
||||
type="text/javascript" |
||||
charset="utf-8" |
||||
></script> |
||||
<script |
||||
src="./lockdown-run.js" |
||||
type="text/javascript" |
||||
charset="utf-8" |
||||
></script> |
||||
<script |
||||
src="./lockdown-more.js" |
||||
type="text/javascript" |
||||
charset="utf-8" |
||||
></script> |
||||
<script src="./phishing-detect.js"></script> |
||||
<link rel="stylesheet" type="text/css" href="./index.css" title="ltr" /> |
||||
<link |
||||
rel="stylesheet" |
||||
type="text/css" |
||||
href="./index-rtl.css" |
||||
title="rtl" |
||||
disabled |
||||
/> |
||||
<style> |
||||
* { |
||||
margin: 0; |
||||
padding: 0; |
||||
box-sizing: border-box; |
||||
} |
||||
body, |
||||
html { |
||||
background-color: var(--color-error-default); |
||||
display: flex; |
||||
flex-direction: column; |
||||
justify-content: center; |
||||
align-items: center; |
||||
font-family: Roboto, Arial, sans-serif; |
||||
width: 100vw; |
||||
min-height: 100vh; |
||||
} |
||||
.content { |
||||
display: flex; |
||||
flex-direction: column; |
||||
align-items: center; |
||||
width: 80%; |
||||
background-color: var(--color-background-default); |
||||
box-shadow: 0 0 15px #737373; |
||||
} |
||||
.content__header { |
||||
display: flex; |
||||
flex-direction: column; |
||||
align-items: center; |
||||
justify-content: center; |
||||
width: 100%; |
||||
color: var(--color-error-default); |
||||
border-bottom: 1px solid var(--color-border-default); |
||||
padding: 2em; |
||||
} |
||||
.content__header h1 { |
||||
font-size: 24px; |
||||
font-weight: normal; |
||||
} |
||||
.content__header h1 i { |
||||
margin-right: 0.25em; |
||||
} |
||||
.content__header img { |
||||
margin-bottom: 3em; |
||||
width: 130px; |
||||
} |
||||
.content__body { |
||||
background-color: var(--color-background-alternative); |
||||
font-size: 12pt; |
||||
color: var(--color-text-default); |
||||
} |
||||
.content__body p { |
||||
margin: 2em; |
||||
} |
||||
.content__body p a { |
||||
text-decoration: underline; |
||||
color: var(--color-primary-default); |
||||
cursor: pointer; |
||||
} |
||||
</style> |
||||
</head> |
||||
<body> |
||||
<div class="content"> |
||||
<div class="content__header"> |
||||
<img src="./images/logo/metamask-fox.svg" alt="MetaMask Logo" /> |
||||
<h1> |
||||
<i class="fa fa-exclamation-circle" aria-hidden="true"></i> |
||||
MetaMask Phishing Detection |
||||
</h1> |
||||
</div> |
||||
<div class="content__body"> |
||||
<p> |
||||
This domain is currently on the MetaMask domain warning list. This |
||||
means that based on information available to us, MetaMask believes |
||||
this domain could currently compromise your security and, as an added |
||||
safety feature, MetaMask has restricted access to the site. To |
||||
override this, please read the rest of this warning for instructions |
||||
on how to continue at your own risk. |
||||
</p> |
||||
<p> |
||||
There are many reasons sites can appear on our warning list, and our |
||||
warning list compiles from other widely used industry lists. Such |
||||
reasons can include known fraud or security risks, such as domains |
||||
that test positive on the |
||||
<a href="https://github.com/metamask/eth-phishing-detect" |
||||
>Ethereum Phishing Detector</a |
||||
>. Domains on these warning lists may include outright malicious |
||||
websites and legitimate websites that have been compromised by a |
||||
malicious actor. |
||||
</p> |
||||
<p> |
||||
To read more about this site |
||||
<a id="csdbLink" href="https://cryptoscamdb.org/search" |
||||
>please search for the domain on CryptoScamDB</a |
||||
>. |
||||
</p> |
||||
<p> |
||||
Note that this warning list is compiled on a voluntary basis. This |
||||
list may be inaccurate or incomplete. Just because a domain does not |
||||
appear on this list is not an implicit guarantee of that domain's |
||||
safety. As always, your transactions are your own responsibility. If |
||||
you wish to interact with any domain on our warning list, you can do |
||||
so by <a id="unsafe-continue">continuing at your own risk</a>. |
||||
</p> |
||||
<p> |
||||
If you think this domain is incorrectly flagged or if a blocked |
||||
legitimate website has resolved its security issues, |
||||
<a |
||||
id="new-issue-link" |
||||
href="https://github.com/metamask/eth-phishing-detect/issues/new" |
||||
>please file an issue</a |
||||
>. |
||||
</p> |
||||
</div> |
||||
</div> |
||||
</body> |
||||
</html> |
@ -1,52 +0,0 @@ |
||||
import querystring from 'querystring'; |
||||
import PortStream from 'extension-port-stream'; |
||||
import browser from 'webextension-polyfill'; |
||||
import createRandomId from '../../shared/modules/random-id'; |
||||
import { setupMultiplex } from './lib/stream-utils'; |
||||
import { getEnvironmentType } from './lib/util'; |
||||
import ExtensionPlatform from './platforms/extension'; |
||||
|
||||
document.addEventListener('DOMContentLoaded', start); |
||||
|
||||
function start() { |
||||
const hash = window.location.hash.substring(1); |
||||
const suspect = querystring.parse(hash); |
||||
|
||||
const newIssueLink = document.getElementById('new-issue-link'); |
||||
const newIssueUrl = `https://github.com/MetaMask/eth-phishing-detect/issues/new`; |
||||
const newIssueParams = `?title=[Legitimate%20Site%20Blocked]%20${encodeURIComponent( |
||||
suspect.hostname, |
||||
)}&body=${encodeURIComponent(suspect.href)}`;
|
||||
newIssueLink.href = `${newIssueUrl}${newIssueParams}`; |
||||
|
||||
global.platform = new ExtensionPlatform(); |
||||
|
||||
const extensionPort = browser.runtime.connect({ |
||||
name: getEnvironmentType(), |
||||
}); |
||||
const connectionStream = new PortStream(extensionPort); |
||||
const mx = setupMultiplex(connectionStream); |
||||
const backgroundConnection = mx.createStream('controller'); |
||||
const continueLink = document.getElementById('unsafe-continue'); |
||||
continueLink.addEventListener('click', () => { |
||||
backgroundConnection.write({ |
||||
jsonrpc: '2.0', |
||||
method: 'safelistPhishingDomain', |
||||
params: [suspect.hostname], |
||||
id: createRandomId(), |
||||
}); |
||||
const redirectTarget = new URL(suspect.href, window.location.href); |
||||
// validate redirect url
|
||||
const invalidProtocol = !['https:', 'http:'].includes( |
||||
redirectTarget.protocol, |
||||
); |
||||
// if in valid, show warning and abort
|
||||
if (invalidProtocol) { |
||||
// we intentionally dont display to the user any potential attacker-written content here
|
||||
console.error(`Invalid redirect url.`); |
||||
return; |
||||
} |
||||
// use the validated url instance
|
||||
window.location.href = redirectTarget.href; |
||||
}); |
||||
} |
@ -0,0 +1,19 @@ |
||||
<!doctype html> |
||||
<html lang="en"> |
||||
<head> |
||||
<title>Mock E2E Phishing Page</title> |
||||
</head> |
||||
<script type="text/javascript"> |
||||
function setIframeSource() { |
||||
const urlSearchParams = new URLSearchParams(window.location.search); |
||||
const params = Object.fromEntries(urlSearchParams.entries()); |
||||
document.getElementById('frame').src = params.extensionUrl; |
||||
} |
||||
window.onload = setIframeSource; |
||||
</script> |
||||
<body> |
||||
<div>Hello</div> |
||||
<iframe id="frame" width=900 height=900> |
||||
</body> |
||||
|
||||
</html> |
@ -0,0 +1,11 @@ |
||||
<!doctype html> |
||||
<html lang="en"> |
||||
<head> |
||||
<title>Mock E2E Phishing Page</title> |
||||
</head> |
||||
<body> |
||||
<div>Hello</div> |
||||
<iframe src="http://127.0.0.1:8081" width=900 height=900> |
||||
</body> |
||||
|
||||
</html> |
@ -0,0 +1,58 @@ |
||||
const path = require('path'); |
||||
const createStaticServer = require('../../development/create-static-server'); |
||||
|
||||
const phishingWarningDirectory = path.resolve( |
||||
__dirname, |
||||
'..', |
||||
'..', |
||||
'node_modules', |
||||
'@metamask', |
||||
'phishing-warning', |
||||
'dist', |
||||
); |
||||
|
||||
class PhishingWarningPageServer { |
||||
constructor() { |
||||
this._server = createStaticServer(phishingWarningDirectory); |
||||
} |
||||
|
||||
async start({ port = 9999 } = {}) { |
||||
this._server.listen(port); |
||||
|
||||
let resolveStart; |
||||
let rejectStart; |
||||
const result = new Promise((resolve, reject) => { |
||||
resolveStart = resolve; |
||||
rejectStart = reject; |
||||
}); |
||||
this._server.once('listening', resolveStart); |
||||
this._server.once('error', rejectStart); |
||||
|
||||
try { |
||||
await result; |
||||
// clean up listener to ensure later errors properly bubble up
|
||||
this._server.removeListener('error', rejectStart); |
||||
} catch (error) { |
||||
this._server.removeListener('listening', resolveStart); |
||||
throw error; |
||||
} |
||||
} |
||||
|
||||
isRunning() { |
||||
return this._server.listening; |
||||
} |
||||
|
||||
async quit() { |
||||
await new Promise((resolve, reject) => |
||||
this._server.close((error) => { |
||||
if (error) { |
||||
reject(error); |
||||
} else { |
||||
resolve(); |
||||
} |
||||
}), |
||||
); |
||||
} |
||||
} |
||||
|
||||
module.exports = PhishingWarningPageServer; |
Loading…
Reference in new issue