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:
Thibaut Sardan
2020-10-13 14:14:55 +02:00
committed by GitHub
co-authored by Github Actions
parent 142cc74d80
commit ae9227ef6d
13 changed files with 176 additions and 9 deletions
@@ -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];
+2
View File
@@ -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
};
+16 -1
View File
@@ -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;
});
}
+1 -1
View File
@@ -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='/'
+2 -1
View File
@@ -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",
+22 -5
View File
@@ -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
});
}
+12 -1
View File
@@ -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: