Add phishing site detection, redirection and information (#488)
* missing type declarations for @polkadot/phishing * phishing landing page * Update packages/extension-base/src/background/types.ts * wording make it short * fix build * add test for xml pdf utils * fix export * Update packages/extension-base/src/utils/isXmlOrPdf.ts * use Trans from i18n * alpha ordering * i18nKey * lint * move pdf and xml detection to enable to prevent unwanted redirection * styled component refactoring * import odering * remove a css hack :) Co-authored-by: Github Actions <action@github.com>
This commit is contained in:
co-authored by
Github Actions
parent
142cc74d80
commit
ae9227ef6d
@@ -8,6 +8,7 @@ import { SignerPayloadJSON, SignerPayloadRaw } from '@polkadot/types/types';
|
||||
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
|
||||
import { RequestAuthorizeTab, ResponseSigning, RequestTypes, ResponseTypes, MessageTypes, ResponseRpcListProviders, RequestRpcSend, RequestRpcSubscribe, RequestRpcUnsubscribe, SubscriptionMessageTypes } from '../types';
|
||||
|
||||
import { PHISHING_PAGE_REDIRECT } from '@polkadot/extension-base/defaults';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
import accountsObservable from '@polkadot/ui-keyring/observable/accounts';
|
||||
import { assert } from '@polkadot/util';
|
||||
@@ -134,7 +135,19 @@ export default class Tabs {
|
||||
return this.#state.rpcUnsubscribe(request, port);
|
||||
}
|
||||
|
||||
private redirectPhishingLanding () {
|
||||
const url = `${chrome.extension.getURL('index.html')}#${PHISHING_PAGE_REDIRECT}`;
|
||||
|
||||
chrome.tabs.update({ url });
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async handle<TMessageType extends MessageTypes> (id: string, type: TMessageType, request: RequestTypes[TMessageType], url: string, port: chrome.runtime.Port): Promise<ResponseTypes[keyof ResponseTypes]> {
|
||||
if (type === 'pub(phishing.redirect)') {
|
||||
return this.redirectPhishingLanding();
|
||||
}
|
||||
|
||||
if (type !== 'pub(authorize.tab)') {
|
||||
this.#state.ensureUrlAuthorized(url);
|
||||
}
|
||||
|
||||
@@ -108,6 +108,7 @@ export interface RequestSignatures {
|
||||
'pub(extrinsic.sign)': [SignerPayloadJSON, ResponseSigning];
|
||||
'pub(metadata.list)': [null, InjectedMetadataKnown[]];
|
||||
'pub(metadata.provide)': [MetadataDef, boolean];
|
||||
'pub(phishing.redirect)': [null, null];
|
||||
'pub(rpc.listProviders)': [void, ResponseRpcListProviders];
|
||||
'pub(rpc.send)': [RequestRpcSend, JsonRpcResponse];
|
||||
'pub(rpc.startProvider)': [string, ProviderMeta];
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
const ALLOWED_PATH = ['/', '/account/restore-json'] as const;
|
||||
const PORT_CONTENT = 'content';
|
||||
const PHISHING_PAGE_REDIRECT = '/phishing-page-detected';
|
||||
const PORT_EXTENSION = 'extension';
|
||||
const PASSWORD_EXPIRY_MIN = 15;
|
||||
const PASSWORD_EXPIRY_MS = PASSWORD_EXPIRY_MIN * 60 * 1000;
|
||||
@@ -11,6 +12,7 @@ export {
|
||||
ALLOWED_PATH,
|
||||
PASSWORD_EXPIRY_MIN,
|
||||
PASSWORD_EXPIRY_MS,
|
||||
PHISHING_PAGE_REDIRECT,
|
||||
PORT_CONTENT,
|
||||
PORT_EXTENSION
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { ResponseTypes, TransportRequestMessage, TransportResponseMessage, MessageTypes, RequestTypes, MessageTypesWithNullRequest, SubscriptionMessageTypes, MessageTypesWithNoSubscriptions, MessageTypesWithSubscriptions } from '../background/types';
|
||||
|
||||
import Injected from './Injected';
|
||||
import { isXmlOrPdf } from '../utils';
|
||||
|
||||
// when sending a message from the injector to the extension, we
|
||||
// - create an event - this we send to the loader
|
||||
@@ -50,7 +51,21 @@ export function sendMessage<TMessageType extends MessageTypes> (message: TMessag
|
||||
|
||||
// the enable function, called by the dapp to allow access
|
||||
export async function enable (origin: string): Promise<Injected> {
|
||||
await sendMessage('pub(authorize.tab)', { origin });
|
||||
const currentPathname = window.location.pathname;
|
||||
|
||||
if (isXmlOrPdf(currentPathname)) {
|
||||
return redirectPhishing();
|
||||
} else {
|
||||
await sendMessage('pub(authorize.tab)', { origin });
|
||||
|
||||
return new Injected(sendMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// the redirection function, called by the page in case the host
|
||||
// is flagged as a phishing site.
|
||||
export async function redirectPhishing (): Promise<Injected> {
|
||||
await sendMessage('pub(phishing.redirect)');
|
||||
|
||||
return new Injected(sendMessage);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2019-2020 @polkadot/extension-base authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export { default as isXmlOrPdf } from './isXmlOrPdf';
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2019-2020 @polkadot/extension-ui authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { isXmlOrPdf } from './';
|
||||
|
||||
describe.only('File extension util function', () => {
|
||||
it('returns true for a xml file', () => {
|
||||
const res = isXmlOrPdf('/some.xml');
|
||||
|
||||
expect(res).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true for a pdf file', () => {
|
||||
const res = isXmlOrPdf('/some.pdf');
|
||||
|
||||
expect(res).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for any other path', () => {
|
||||
expect(isXmlOrPdf('/some-pdf')).toBe(false);
|
||||
expect(isXmlOrPdf('/some-xml')).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2019-2020 @polkadot/extension authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export default function (pathname: string): boolean {
|
||||
const prohibitedFiles: RegExp[] = [
|
||||
/\.xml$/u,
|
||||
/\.pdf$/u,
|
||||
/1281/u
|
||||
];
|
||||
|
||||
return prohibitedFiles.some((file) => {
|
||||
if (file.test(pathname)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
@@ -10,7 +10,7 @@ export function injectExtension (enable: (origin: string) => Promise<Injected>,
|
||||
// small helper with the typescript types, just cast window
|
||||
const windowInject = window as Window & InjectedWindow;
|
||||
|
||||
// don't clobber the existing object, we will add it it (or create as needed)
|
||||
// don't clobber the existing object, we will add it (or create as needed)
|
||||
windowInject.injectedWeb3 = windowInject.injectedWeb3 || {};
|
||||
|
||||
// add our enable function
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2019-2020 @polkadot/extension-ui authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { ThemeProps } from '../types';
|
||||
|
||||
import React from 'react';
|
||||
import { Trans } from 'react-i18next';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import useTranslation from '../hooks/useTranslation';
|
||||
import { Header } from '../partials';
|
||||
|
||||
interface Props extends ThemeProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function PhishingDetected ({ className }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header text={t<string>('Phishing detected')} />
|
||||
<div className={className}>
|
||||
<p>
|
||||
{t<string>(`You have been redirected because the Polkadot{.js} extension believes that this website could compromise the
|
||||
security of your accounts.`)}
|
||||
</p>
|
||||
<p>
|
||||
{t<string>(`
|
||||
The redirection could also happen on an outright malicious website or on a legitimate websites that has been compromised and flagged.
|
||||
`)}
|
||||
</p>
|
||||
<p>
|
||||
{t<string>(`
|
||||
This redirection is based on a list of websites accessible at https://github.com/polkadot-js/phishing. Note that this is a community-driven, curated list.
|
||||
It might be incomplete or inaccurate.
|
||||
`)}
|
||||
</p>
|
||||
<p>
|
||||
<Trans i18nKey='phishing.incorrect'>
|
||||
If you think that this website was flagged incorrectly, <a href='https://github.com/polkadot-js/phishing/issues/new'>please open an issue by clicking here</a>.
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default styled(PhishingDetected)(({ theme }: Props) => `
|
||||
p {
|
||||
color: ${theme.subTextColor};
|
||||
margin-bottom: 1rem;
|
||||
margin-top: 0;
|
||||
|
||||
a {
|
||||
color: ${theme.subTextColor};
|
||||
}
|
||||
}
|
||||
`);
|
||||
@@ -6,6 +6,7 @@ import { SettingsStruct } from '@polkadot/ui-settings/types';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Route, Switch } from 'react-router';
|
||||
import { PHISHING_PAGE_REDIRECT } from '@polkadot/extension-base/defaults';
|
||||
import uiSettings from '@polkadot/ui-settings';
|
||||
import { setSS58Format } from '@polkadot/util-crypto';
|
||||
|
||||
@@ -22,6 +23,7 @@ import Export from './Export';
|
||||
import Forget from './Forget';
|
||||
import ImportQr from './ImportQr';
|
||||
import ImportSeed from './ImportSeed';
|
||||
import PhishingDetected from './PhishingDetected';
|
||||
import RestoreJson from './RestoreJson';
|
||||
import Metadata from './Metadata';
|
||||
import Signing from './Signing';
|
||||
@@ -136,6 +138,7 @@ export default function Popup (): React.ReactElement {
|
||||
<Route path='/account/restore-json'>{wrapWithErrorBoundary(<RestoreJson />, 'restore-json')}</Route>
|
||||
<Route path='/account/derive/:address/locked'>{wrapWithErrorBoundary(<Derive isLocked />, 'derived-address-locked')}</Route>
|
||||
<Route path='/account/derive/:address'>{wrapWithErrorBoundary(<Derive />, 'derive-address')}</Route>
|
||||
<Route path={PHISHING_PAGE_REDIRECT}>{wrapWithErrorBoundary(<PhishingDetected />, 'phishing-page-redirect')}</Route>
|
||||
<Route
|
||||
exact
|
||||
path='/'
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"@polkadot/extension-base": "0.35.0-beta.12",
|
||||
"@polkadot/extension-inject": "0.35.0-beta.12",
|
||||
"@polkadot/extension-ui": "0.35.0-beta.12"
|
||||
"@polkadot/extension-ui": "0.35.0-beta.12",
|
||||
"@polkadot/phishing": "^0.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@polkadot/dev": "^0.57.1",
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
|
||||
import { Message } from '@polkadot/extension-base/types';
|
||||
|
||||
import { enable, handleResponse } from '@polkadot/extension-base/page';
|
||||
import { enable, handleResponse, redirectPhishing } from '@polkadot/extension-base/page';
|
||||
import { injectExtension } from '@polkadot/extension-inject';
|
||||
import retrieveCheckDeny from '@polkadot/phishing';
|
||||
|
||||
// setup a response listener (events created by the loader for extension responses)
|
||||
window.addEventListener('message', ({ data, source }: Message): void => {
|
||||
@@ -21,7 +22,23 @@ window.addEventListener('message', ({ data, source }: Message): void => {
|
||||
}
|
||||
});
|
||||
|
||||
injectExtension(enable, {
|
||||
name: 'polkadot-js',
|
||||
version: process.env.PKG_VERSION as string
|
||||
});
|
||||
const currentUrl = window.location.host;
|
||||
|
||||
retrieveCheckDeny(currentUrl)
|
||||
.then((isOnDeny) => {
|
||||
if (isOnDeny) {
|
||||
console.log('Phishing detected, redirecting to phishing info landing page');
|
||||
redirectPhishing().catch(console.error);
|
||||
} else {
|
||||
inject();
|
||||
}
|
||||
}).catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
|
||||
function inject () {
|
||||
injectExtension(enable, {
|
||||
name: 'polkadot-js',
|
||||
version: process.env.PKG_VERSION as string
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2075,6 +2075,7 @@ __metadata:
|
||||
"@polkadot/extension-base": 0.35.0-beta.12
|
||||
"@polkadot/extension-inject": 0.35.0-beta.12
|
||||
"@polkadot/extension-ui": 0.35.0-beta.12
|
||||
"@polkadot/phishing": ^0.1.2
|
||||
babel-loader: ^8.1.0
|
||||
extract-loader: ^5.1.0
|
||||
file-loader: ^6.1.0
|
||||
@@ -2112,6 +2113,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@polkadot/phishing@npm:^0.1.2":
|
||||
version: 0.1.2
|
||||
resolution: "@polkadot/phishing@npm:0.1.2"
|
||||
dependencies:
|
||||
"@babel/runtime": ^7.11.2
|
||||
"@polkadot/x-fetch": 0.3.2
|
||||
checksum: 6b5d35406284df4d33205565f214cdcc2ee5f7f1930c342183922bf5a2b16698db893994cfabe9309d6b847cad8f44548c4758a18875f9d8b557105481a9105a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@polkadot/react-identicon@npm:^0.60.0-beta.0":
|
||||
version: 0.60.0-beta.0
|
||||
resolution: "@polkadot/react-identicon@npm:0.60.0-beta.0"
|
||||
@@ -2330,7 +2341,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@polkadot/x-fetch@npm:^0.3.2":
|
||||
"@polkadot/x-fetch@npm:0.3.2, @polkadot/x-fetch@npm:^0.3.2":
|
||||
version: 0.3.2
|
||||
resolution: "@polkadot/x-fetch@npm:0.3.2"
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user