Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add websocket support for c2 detection #28782

Merged
merged 30 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
eefa3cb
feat: add websocket support for c2 detection
AugmentedMode Nov 27, 2024
60a3ac7
Merge branch 'develop' into feat/websocket-support
AugmentedMode Nov 27, 2024
6f1da54
fix: add support to v2 manifest
AugmentedMode Nov 27, 2024
d2c7f78
Merge branch 'feat/websocket-support' of https://github.com/MetaMask/…
AugmentedMode Nov 27, 2024
825913b
chore: add tests for blocking websocket requests
AugmentedMode Dec 2, 2024
84c4a45
Merge branch 'main' into feat/websocket-support
AugmentedMode Dec 2, 2024
b7d45d1
fix: tests
AugmentedMode Dec 2, 2024
4afc74f
Merge branch 'feat/websocket-support' of https://github.com/MetaMask/…
AugmentedMode Dec 2, 2024
7726297
Merge branch 'main' into feat/websocket-support
AugmentedMode Dec 2, 2024
3445962
chore: remove unused var
AugmentedMode Dec 2, 2024
648bfbb
Merge branch 'feat/websocket-support' of https://github.com/MetaMask/…
AugmentedMode Dec 2, 2024
395bf8c
Merge branch 'main' into feat/websocket-support
AugmentedMode Dec 2, 2024
f350970
chore: fix test name for malicious c2
AugmentedMode Dec 3, 2024
b24bcf9
chore: fix name
AugmentedMode Dec 3, 2024
d02b738
chore: cleanup
AugmentedMode Dec 3, 2024
47e8249
feat: add webocket support to fixtures
AugmentedMode Dec 9, 2024
bb5505d
chore: cleanup
AugmentedMode Dec 9, 2024
e52d45c
chore: add context to c2 hash in mocks
AugmentedMode Dec 9, 2024
612c011
Merge branch 'main' into feat/websocket-support
AugmentedMode Dec 9, 2024
a3dced8
fix: add promise to websocket close
AugmentedMode Dec 9, 2024
92d98a5
Merge branch 'main' into feat/websocket-support
AugmentedMode Dec 9, 2024
e1bec16
Merge branch 'main' into feat/websocket-support
AugmentedMode Dec 9, 2024
dabc7a8
fix: await for websocket close hangs
AugmentedMode Dec 9, 2024
627619c
fix: now mocks the websocket server
AugmentedMode Dec 10, 2024
ec12780
Merge branch 'main' into feat/websocket-support
AugmentedMode Dec 10, 2024
d64c13c
chore: cleanup
AugmentedMode Dec 10, 2024
32ffc04
Merge branch 'main' into feat/websocket-support
AugmentedMode Dec 11, 2024
fe74a38
chore: remove unused var
AugmentedMode Dec 11, 2024
d11f81a
fix: move websocket mock to specific tests for flexibility
AugmentedMode Dec 11, 2024
28c26bf
Merge branch 'main' into feat/websocket-support
AugmentedMode Dec 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/manifest/v2/_base.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@
"clipboardWrite",
"http://*/*",
"https://*/*",
"ws://*/*",
"wss://*/*",
"activeTab",
"webRequest",
"webRequestBlocking",
Expand Down
4 changes: 3 additions & 1 deletion app/manifest/v3/_base.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@
"http://localhost:8545/",
"file://*/*",
"http://*/*",
"https://*/*"
"https://*/*",
"ws://*/*",
"wss://*/*"
AugmentedMode marked this conversation as resolved.
Show resolved Hide resolved
],
"icons": {
"16": "images/icon-16.png",
Expand Down
2 changes: 1 addition & 1 deletion app/scripts/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ function maybeDetectPhishing(theController) {
return {};
},
{
urls: ['http://*/*', 'https://*/*'],
urls: ['http://*/*', 'https://*/*', 'ws://*/*', 'wss://*/*'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this change needs automated tests

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added a few tests!

},
isManifestV2 ? ['blocking'] : [],
);
Expand Down
3 changes: 2 additions & 1 deletion privacy-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,6 @@
"unresponsive-rpc.test",
"unresponsive-rpc.url",
"user-storage.api.cx.metamask.io",
"www.4byte.directory"
"www.4byte.directory",
"verify.walletconnect.com"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was added as MetaMasks Test dApp makes a websocket connnection as shown in the screenshot

Screenshot 2024-12-10 at 3 33 47 PM

]
44 changes: 44 additions & 0 deletions test/e2e/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const BigNumber = require('bignumber.js');
const mockttp = require('mockttp');
const detectPort = require('detect-port');
const { difference } = require('lodash');
const WebSocket = require('ws');
const createStaticServer = require('../../development/create-static-server');
const { setupMocking } = require('./mock-e2e');
const { Ganache } = require('./seeder/ganache');
Expand Down Expand Up @@ -640,6 +641,48 @@ async function unlockWallet(
}
}

/**
* Simulates a WebSocket connection by executing a script in the browser context.
*
* @param {WebDriver} driver - The WebDriver instance.
* @param {string} hostname - The hostname to connect to.
*/
async function createWebSocketConnection(driver, hostname) {
try {
await driver.executeScript(async (wsHostname) => {
const url = `ws://${wsHostname}:8000`;

const socket = new WebSocket(url);

socket.onopen = () => {
console.log('WebSocket connection opened');
socket.send('Hello, server!');
};

socket.onerror = (error) => {
console.error(
'WebSocket error:',
error.message || 'Connection blocked',
);
};

socket.onmessage = (event) => {
console.log('Message received from server:', event.data);
};

socket.onclose = () => {
console.log('WebSocket connection closed');
};
}, hostname);
} catch (error) {
console.error(
`Failed to execute WebSocket connection script for ws://${hostname}:8081`,
error,
);
throw error;
}
}

const logInWithBalanceValidation = async (driver, ganacheServer) => {
await unlockWallet(driver);
// Wait for balance to load
Expand Down Expand Up @@ -975,4 +1018,5 @@ module.exports = {
tempToggleSettingRedesignedTransactionConfirmations,
openMenuSafe,
sentryRegEx,
createWebSocketConnection,
};
2 changes: 2 additions & 0 deletions test/e2e/mock-e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ async function setupMocking(
},
});

await server.forAnyWebSocket().thenEcho();

const mockedEndpoint = await testSpecificMock(server);
// Mocks below this line can be overridden by test-specific mocks

Expand Down
19 changes: 9 additions & 10 deletions test/e2e/tests/phishing-controller/mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ const {
const lastUpdated = 1;
const defaultHotlist = { data: [] };
const defaultC2DomainBlocklist = {
recentlyAdded: [],
recentlyAdded: [
'33c8e026e76cea2df82322428554c932961cd80080fa379454350d7f13371f36', // hash for malicious.localhost
],
recentlyRemoved: [],
lastFetchedAt: '2024-08-27T15:30:45Z',
};
Expand Down Expand Up @@ -95,15 +97,12 @@ async function setupPhishingDetectionMocks(
};
});

await mockServer
.forGet(C2_DOMAIN_BLOCKLIST_URL)
.withQuery({ timestamp: '2024-08-27T15:30:45Z' })
.thenCallback(() => {
return {
statusCode: 200,
json: defaultC2DomainBlocklist,
};
});
await mockServer.forGet(C2_DOMAIN_BLOCKLIST_URL).thenCallback(() => {
return {
statusCode: 200,
json: defaultC2DomainBlocklist,
};
});

await mockServer
.forGet('https://github.com/MetaMask/eth-phishing-detect/issues/new')
Expand Down
78 changes: 77 additions & 1 deletion test/e2e/tests/phishing-controller/phishing-detection.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ const { strict: assert } = require('assert');
const { createServer } = require('node:http');
const { createDeferredPromise } = require('@metamask/utils');
const { until } = require('selenium-webdriver');

const {
defaultGanacheOptions,
withFixtures,
openDapp,
unlockWallet,
WINDOW_TITLES,
createWebSocketConnection,
} = require('../../helpers');
const FixtureBuilder = require('../../fixture-builder');
const {
Expand Down Expand Up @@ -315,6 +315,82 @@ describe('Phishing Detection', function () {
);
});

it('should block a website that makes a websocket connection to a malicious command and control server', async function () {
const testPageURL = 'http://localhost:8080';

await withFixtures(
{
fixtures: new FixtureBuilder().build(),
ganacheOptions: defaultGanacheOptions,
title: this.test.fullTitle(),
testSpecificMock: async (mockServer) => {
await setupPhishingDetectionMocks(mockServer, {
blockProvider: BlockProvider.MetaMask,
});
},
dapp: true,
enableWebSocketServer: true,
},
async ({ driver }) => {
await unlockWallet(driver);

await driver.openNewPage(testPageURL);

await createWebSocketConnection(driver, 'malicious.localhost');

await driver.switchToWindowWithTitle(
'MetaMask Phishing Detection',
10000,
);

await driver.waitForSelector({
testId: 'unsafe-continue-loaded',
});

await driver.clickElement({
text: 'Back to safety',
});

const currentUrl = await driver.getCurrentUrl();
const expectedPortfolioUrl = `https://portfolio.metamask.io/?metamaskEntry=phishing_page_portfolio_button`;

assert.equal(currentUrl, expectedPortfolioUrl);
},
);
});

it('should not block a website that makes a safe WebSocket connection', async function () {
const testPageURL = 'http://localhost:8080/';

await withFixtures(
{
fixtures: new FixtureBuilder().build(),
ganacheOptions: defaultGanacheOptions,
title: this.test.fullTitle(),
testSpecificMock: async (mockServer) => {
await setupPhishingDetectionMocks(mockServer, {
blockProvider: BlockProvider.MetaMask,
});
},
dapp: true,
enableWebSocketServer: true,
},
async ({ driver }) => {
await unlockWallet(driver);

await driver.openNewPage(testPageURL);

await createWebSocketConnection(driver, 'safe.localhost');

await driver.wait(until.titleIs(WINDOW_TITLES.TestDApp), 10000);

const currentUrl = await driver.getCurrentUrl();

assert.equal(currentUrl, testPageURL);
},
);
});

describe('Phishing redirect protections', function () {
/**
* Status codes 305 (via Location header) and 306 (Set-Proxy) header do not
Expand Down
Loading