4 |
5 | # PhishDetect Extension
6 |
7 | This is a browser extension for Mozilla Firefox and Google Chrome. It is a client for PhishDetect and it is currently capable of the following functionality:
8 |
9 | - It regularly fetches a list of bad indicators from the configured PhishDetect Node.
10 | - It blocks any visits to websites whose domains match a known indicator (inspired by the [Blockade](https://github.com/blockadeio) project).
11 | - It integrates in various supported webmails. Everytime an email is opened it checks for known bad senders as well as known bad links inside the body.
12 | - It modifies the links contained in the body in order to display a dialog prompt that offers the user the possibility of scanning the links with PhishDetect.
13 | - It creates a button in the webmails' web interface to allow to share the full source email with the Node operators.
14 | - It creates context menus and a toolbar button that allow to either send a link or directly the HTML content of the opened page to a PhishDetect backend in order to be scanned for phishing.
15 | - It allows to scan the browsing history to identify visits to blocklisted domains.
16 |
17 | Currently supported webmails:
18 |
19 | - Gmail
20 | - Roundcube
21 |
22 | ## How to use
23 |
24 | For details on how to install, configure and use the PhishDetect Extension you can consult the [Help](https://phishdetect.io/help/) pages.
25 |
26 |
27 | ## Build
28 |
29 | First install node.js:
30 |
31 | $ sudo apt-get install nodejs
32 |
33 | Then install yarn as explained in the [official instructions](https://classic.yarnpkg.com/en/docs/install#debian-stable).
34 |
35 | You can now build the extension by simply launching `make`:
36 |
37 | $ make
38 |
39 | The extension is now available in the `build/` folder and it is ready to be loaded or packaged. For the latter, we can use the following command to obtain a `phishdetect.zip` file for distribution:
40 |
41 | $ make package
42 |
43 |
44 | ## License
45 |
46 | PhishDetect Extension is released under GNU General Public License 3.0 and is copyrighted to Claudio Guarnieri.
47 |
--------------------------------------------------------------------------------
/_locales/en/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifestDescription": {
3 | "message": "Anti-phishing browser extension. Scan suspicious pages and links.",
4 | "description": "Displayed in the Chrome/FF store and about:extensions"
5 | },
6 | "popupScanThisPage": {
7 | "message": "Scan this page!",
8 | "description": "Displayed on the popup page button."
9 | },
10 | "popupScanHistory": {
11 | "message": "Scan browsing history",
12 | "description": "Link for launching scan of browsing history on popup page."
13 | },
14 | "popupSettings": {
15 | "message": "Settings",
16 | "description": "Label for a button on the popup page to open the settings page."
17 | },
18 | "popupHelp": {
19 | "message": "Help",
20 | "description": "Help link on popup page."
21 | },
22 | "optionsDescription": {
23 | "message": "Through this form you can decide which PhishDetect Node to connect to.",
24 | "description": "Description shown at the top of the options page"
25 | },
26 | "optionsNode": {
27 | "message": "Your preferred PhishDetect Node:",
28 | "description": "Label for the PhishDetect node form field on the options page"
29 | },
30 | "optionsEnableWebmail": {
31 | "message": "Enable Webmails integration",
32 | "description": "Label for checkbox to enable webmail integration."
33 | },
34 | "optionsSendAlerts": {
35 | "message": "Automatically send alerts to PhishDetect Node",
36 | "description": "Label for checkbox to enable automatically sharing of alerts."
37 | },
38 | "optionsContact": {
39 | "message": "If you wish, you can leave your contact details:",
40 | "description": "Label for contact details field."
41 | },
42 | "optionsSave": {
43 | "message": "Save",
44 | "description": "Label for save button on options page."
45 | },
46 | "optionsRestore": {
47 | "message": "Restore Defaults",
48 | "description": "Link to restore default values on the options page."
49 | },
50 | "historyScanInProgress": {
51 | "message": "Scan in progress...",
52 | "description": "Text displayed when browsing history scan is in progress."
53 | },
54 | "historyScanCompleted": {
55 | "message": "Scan completed!",
56 | "description": "Text displayed when browsing history scan is completed."
57 | },
58 | "historyNothingFound": {
59 | "message": "Nothing suspicious found!",
60 | "description": "Text displayed when scan is completed and no results were found."
61 | },
62 | "registerLabelName": {
63 | "message": "Your name and affiliation:",
64 | "description": "Label to the registration form input for user name"
65 | },
66 | "registerLabelEmail": {
67 | "message": "Your email address:",
68 | "description": "Label to the registration form input for user email"
69 | },
70 | "registerButtonRegister": {
71 | "message": "Register",
72 | "description": "Label to the registration form confirmation button"
73 | },
74 | "registerErrorInvalidName": {
75 | "message": "You did not enter a valid name and affiliation",
76 | "description": "Error message shown when the user enters an invalid name and affiliation"
77 | },
78 | "registerErrorInvalidEmail": {
79 | "message": "You entered an invalid email address!",
80 | "description": "Error message shown when the user enters an invalid email address"
81 | },
82 | "apikeyGetToken": {
83 | "message": "In order to continue using the PhishDetect Browser Extension, you will be required to enter a valid secret token. You don't have one yet?",
84 | "description": "Paragraph text. Following this text is a link to register for a token"
85 | },
86 | "apikeyRegister": {
87 | "message": "Register here!",
88 | "description": "Text for a link to the registration page"
89 | },
90 | "apikeyNode": {
91 | "message": "Your preferred PhishDetect Node:",
92 | "description": "Label for a text input for the url of your PhishDetect node"
93 | },
94 | "apikeyToken": {
95 | "message": "Enter your secret token:",
96 | "description": "Label for a text input for your secret token"
97 | },
98 | "apikeySave": {
99 | "message": "Save",
100 | "description": "Text for button to save api token and node settings"
101 | },
102 | "contextMenuReportPage": {
103 | "message": "Report this page as suspicious",
104 | "description": "Text for context menu option to report a page"
105 | },
106 | "contextMenuReportLink": {
107 | "message": "Report this link as suspicious",
108 | "description": "Text for context menu option to report a link"
109 | },
110 | "contextMenuScanPage": {
111 | "message": "Scan this page for phishing",
112 | "description": "Text for context menu option to scan a page"
113 | },
114 | "contextMenuScanLink": {
115 | "message": "Scan this link for phishing",
116 | "description": "Text for context menu option to scan a link"
117 | },
118 | "webmailWarningWarning": {
119 | "message": "Warning",
120 | "description": "Label for a warning message"
121 | },
122 | "webmailWarningPleaseBeCautious": {
123 | "message": "Please be cautious!",
124 | "description": "Secondary text for a warning message"
125 | },
126 | "webmailWarningSender": {
127 | "message": "The email was sent by a known malicious address.",
128 | "description": "Explanation of a webmail warning event"
129 | },
130 | "webmailWarningLinks": {
131 | "message": "The email contains known malicious links.",
132 | "description": "Explanation of a webmail warning event"
133 | },
134 | "webmailWarningHelp": {
135 | "message": "For more information visit our help page:",
136 | "description": "Label for a link to the help page."
137 | },
138 | "webmailLinkWarning": {
139 | "message": "PhishDetect Warning: this link is malicious!",
140 | "description": "Warning text for a known malicious link"
141 | },
142 | "webmailDialog": {
143 | "message": "How do you want to open this link?",
144 | "description": "Warning text for a known malicious link"
145 | },
146 | "webmailDialogDirectly": {
147 | "message": "Directly",
148 | "description": "Button to open a link 'Directly' / 'Unsafely'"
149 | },
150 | "webmailDialogSafely": {
151 | "message": "Safely",
152 | "description": "Button to open a link 'Safely'"
153 | },
154 | "webmailPreview": {
155 | "message": "You are about to open this link. Do you want to continue?",
156 | "description": "Confirmation dialog text when opening a link"
157 | },
158 | "webmailPreviewContinue": {
159 | "message": "Continue",
160 | "description": "Button text to continue visit to a link"
161 | },
162 | "webmailPreviewCancel": {
163 | "message": "No",
164 | "description": "Button text to cancel visit to a link"
165 | },
166 | "reportEmailReportedAlready": {
167 | "message": "Reported to PhishDetect",
168 | "description": "Text shown when trying to report an email that was already reported."
169 | },
170 | "reportEmailReport": {
171 | "message": "Report to PhishDetect",
172 | "description": "Label for the report button"
173 | },
174 | "reportEmailConfirm": {
175 | "message": "Are you sure you want to report this email to your PhishDetect Node administrator?",
176 | "description": "Confirmation message when reporting an email"
177 | },
178 | "popupReportThisPage": {
179 | "message": "Report this page",
180 | "description": "Button to report the page from a popup"
181 | },
182 | "popupReported": {
183 | "message": "Reported!",
184 | "description": "Shown in a popup when an email is reported."
185 | },
186 | "popupScanning": {
187 | "message": "Scanning...",
188 | "description": "Shown in a popup when an email is being scanned."
189 | },
190 | "popupTokenRequired": {
191 | "message": "This PhishDetect Node requires users to register a secret token.",
192 | "description": "Paragraph in a popup explaining the need for a token"
193 | },
194 | "popupActivate": {
195 | "message": "Continue here to activate your browser!",
196 | "description": "Link text to register for a secret token to activate PhishDetect"
197 | },
198 | "apikeyErrorSecretToken": {
199 | "message": "You did not provide a valid secret token.",
200 | "description": "Error text when no secret token is entered in the apikey form"
201 | },
202 | "serverOfflineWarning": {
203 | "message": "The PhishDetect node is offline or your Internet connection is down.",
204 | "description": "Error when PhishDetect node is offline or incorrect"
205 | },
206 | "serverOfflineFormError": {
207 | "message": "This PhishDetect node is unreachable. Confirm that you have typed the address correctly.",
208 | "description": "Error when PhishDetect node is offline or incorrect"
209 | },
210 | "serverUnauthorizedWarning": {
211 | "message": "Error authenticating to the PhishDetect node. Check that you secret token is correct and valid for this server.",
212 | "description": "Error when an unauthorized error is returned from the PhishDetect server."
213 | },
214 | "apikeySaved": {
215 | "message": "Saved!",
216 | "description": "Displayed when an apikey is saved succesfully"
217 | },
218 | "optionsSaved": {
219 | "message": "Saved!",
220 | "description": "Displayed when settings are saved succesfully"
221 | },
222 | "optionsSettings": {
223 | "message": "Settings",
224 | "description": "Heading for the form on the settings page."
225 | },
226 | "reportThankYou": {
227 | "message": "Thank you for your report! We will review it as soon as possible and take action if required. By reporting suspicious links and emails you help to protect others like you!",
228 | "description": "Thank you message when user reports a link or email"
229 | },
230 | "reportReportedURL": {
231 | "message": "You reported the following link as suspicious:",
232 | "description": "URL report description"
233 | },
234 | "reportReportedEmail": {
235 | "message": "You reported a suspicious email!",
236 | "description": "Email report description"
237 | },
238 | "scanResultsLinkAnalyzed": {
239 | "message": "We analyzed the link:",
240 | "description": ""
241 | },
242 | "scanResultDangerous": {
243 | "message": "While this is a legitimate $brand$ site, we advise caution. Some services offer functionality often abused by malicious attackers. This link leads to one such risky legitimate services, therefore we are not able to automatically determine whether it's safe to proceed.",
244 | "description": "Recommendation when an analyzed website turns out to be potentially dangerous",
245 | "placeholders": {
246 | "brand": {
247 | "content": "$1",
248 | "example": "Google"
249 | }
250 | }
251 | },
252 | "scanResultsDangerousExamples": {
253 | "message": "Following are some examples:",
254 | "description": "Label for list of examples of potentially dangerous legitimate links"
255 | },
256 | "scanResultsDangerousExample1": {
257 | "message": "Services like Google or Microsoft allow for third-party applications to connect to your account (for example, to import invitations into an external calendar service). An attacker might try to obtain access to your account by tricking you into granting a malicious third-party application access to it.",
258 | "description": "Dangerous link example #1"
259 | },
260 | "scanResultsDangerousExample2": {
261 | "message": "Google, for example, offers to host pages at sites.google.com. Attackers often abuse this service to serve their phishing websites.",
262 | "description": "Dangerous link example #2"
263 | },
264 | "scanResultsSuspicious": {
265 | "message": "This website has suspicious properties. This could be a $brand$ phishing site, but we couldn't determine more conclusively. Please proceed with caution.",
266 | "description": "Message displayed when an analysis returns a yellow warning",
267 | "placeholders": {
268 | "brand": {
269 | "content": "$1",
270 | "example": "Google"
271 | }
272 | }
273 | },
274 | "scanResultsBad": {
275 | "message": "This website has very suspicious properties. This is highly likely a $brand$ phishing site. We recommend to not proceed to this page.",
276 | "description": "Message displayed when analysis returns a red warning",
277 | "placeholders": {
278 | "brand": {
279 | "content": "$1",
280 | "example": "Google"
281 | }
282 | }
283 | },
284 | "scanResultsStored": {
285 | "message": "The results of this scan were stored for operators to review and take action.",
286 | "description": "Notice displayed when analysis returns a warning"
287 | },
288 | "scanResultsWarnings": {
289 | "message": "Warnings",
290 | "description": "Label for list of warnings displayed in scan results page"
291 | },
292 | "scanResultsRedirectedTo": {
293 | "message": "The original link redirected to the URL",
294 | "description": "Message shown when an analyzed link redirected to a second one"
295 | },
296 | "scanResultsScreenshot": {
297 | "message": "Following is a screenshot preview of the website:",
298 | "description": "Label for screenshot preview displayed in scan results page"
299 | },
300 | "scanResultsContinueAtMyOwnRisk": {
301 | "message": "Continue anyway at my own risk!",
302 | "description": "Label for button in scan results page when link was detected as suspicious"
303 | },
304 | "scanResultsSafelisted": {
305 | "message": "The domain the link leads to is safelisted! It appears to be a legitimate $brand$ site.",
306 | "description": "Message displayed when the link analyzed is found as safelisted",
307 | "placeholders": {
308 | "brand": {
309 | "content": "$1",
310 | "example": "Google"
311 | }
312 | }
313 | },
314 | "scanResultsNothingSuspicious": {
315 | "message": "No suspicious elements have been found in this page.",
316 | "description": "Message displayed when the scan didn't find anything suspicious"
317 | },
318 | "scanResultsRemainCautious": {
319 | "message": "Please notice: this does not guarantee that the page is completely safe (for example, it might evade our detection or identify our service and redirect instead to a legitimate page), please always be cautious.",
320 | "description": "Message displayed when the scan didn't find anything suspicious, but to make sure the user takes this with a gain of salt"
321 | },
322 | "scanResultsNotStored": {
323 | "message": "The results of this scan were not stored.",
324 | "description": "Notice displayed when the scan didn't find anything suspicious, to inform that results were not stored"
325 | },
326 | "scanResultsIfYouThinkWrong": {
327 | "message": "If you believe this assessment is wrong, and suspect this page to be suspicious",
328 | "description": "Message to solicit a report if user thinks assessment is wrong"
329 | },
330 | "scanResultsPleaseReport": {
331 | "message": "please report it to us!",
332 | "description": "Link to report a link that was not detected"
333 | },
334 | "scanResultsThankYouForReport": {
335 | "message": "Thank you for reporting this to us!",
336 | "description": "Message displayed when a user decided to report an inconclusive scan result"
337 | },
338 | "scanResultsContinueNow": {
339 | "message": "Continue to the link now",
340 | "description": "Label for button in scan results page when link was not detected"
341 | }
342 | }
343 |
--------------------------------------------------------------------------------
/graphics/gmail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/graphics/gmail.png
--------------------------------------------------------------------------------
/graphics/redirect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/graphics/redirect.png
--------------------------------------------------------------------------------
/graphics/warning.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/graphics/warning.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "phishdetect",
3 | "version": "2020.10.23",
4 | "description": "Anti-phishing browser extension. Scan suspicious pages and links.",
5 | "main": "",
6 | "scripts": {
7 | "build": "webpack",
8 | "start": "node utils/webserver.js"
9 | },
10 | "author": "Claudio Guarnieri",
11 | "license": "GPL-3.0",
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/phishdetect/phishdetect-extension.git"
15 | },
16 | "dependencies": {
17 | "@fortawesome/fontawesome-free": "^5.15.4",
18 | "@fortawesome/fontawesome-svg-core": "^1.3.0",
19 | "@fortawesome/free-solid-svg-icons": "^6.0.0",
20 | "@fortawesome/react-fontawesome": "^0.1.17",
21 | "autoprefixer": "^10.4.2",
22 | "gmail-js": "^1.0.17",
23 | "jquery": "^3.6.0",
24 | "js-sha256": "^0.9.0",
25 | "npm": "^8.11.0",
26 | "postcss": "^8.4.8",
27 | "react": "^17.0.2",
28 | "react-dom": "^17.0.2",
29 | "tailwindcss": "^2.2.14",
30 | "tldts": "^5.7.69",
31 | "validator": "^13.7.0",
32 | "vex-dialog": "^1.1.0",
33 | "vex-js": "^4.1.0"
34 | },
35 | "devDependencies": {
36 | "@babel/core": "^7.15.0",
37 | "@babel/preset-env": "^7.15.0",
38 | "@babel/preset-react": "^7.14.5",
39 | "babel-loader": "8.2.2",
40 | "clean-webpack-plugin": "^3.0.0",
41 | "copy-webpack-plugin": "^9.0.1",
42 | "css-loader": "6.2.0",
43 | "eslint": "^7.32.0",
44 | "eslint-plugin-react": "^7.25.1",
45 | "file-loader": "6.2.0",
46 | "fs-extra": "10.0.0",
47 | "html-loader": "2.1.2",
48 | "html-webpack-plugin": "5.3.2",
49 | "style-loader": "^3.2.1",
50 | "webpack": "5.51.1",
51 | "webpack-cli": "4.8.0",
52 | "webpack-dev-server": "^4.1.0",
53 | "write-file-webpack-plugin": "4.5.1"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/ConfirmationDialog.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | import React from "react";
19 |
20 | export default function ConfirmationDialog(props) {
21 | return (
22 |
23 | PhishDetect
24 | {chrome.i18n.getMessage("reportEmailConfirm")}
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/HistoryAlerts.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | import React from "react";
19 |
20 | function HistoryAlert(props) {
21 | let { dateTime, url } = props;
22 |
23 | return (
24 |
{dateTime} suspicious visit to {url}
25 | );
26 | }
27 |
28 | export default class HistoryAlerts extends React.Component {
29 | constructor(props) {
30 | super(props);
31 |
32 | this.state = {
33 | alerts: [],
34 | };
35 | }
36 |
37 | onClearAlerts() {
38 | this.setState({alerts: []});
39 | }
40 |
41 | onAddAlert(dateTime, url) {
42 | const alert = React.createElement(HistoryAlert, {dateTime, url});
43 | this.setState({
44 | alerts: this.state.alerts.concat(alert),
45 | });
46 | }
47 |
48 | render() {
49 | return (
50 | this.state.alerts.map(alert => {
51 | return alert;
52 | })
53 | );
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/components/ReportEmailButton.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | import React from "react";
19 | const vex = require("vex-js");
20 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
21 | import { faFish, faCheckCircle } from "@fortawesome/free-solid-svg-icons";
22 |
23 | function WebmailButton(props) {
24 | let { className, icon, message, onClick } = props;
25 |
26 | return (
27 |
28 |
29 | {message}
30 |
31 | );
32 | }
33 |
34 | export default class ReportEmailButton extends React.Component {
35 | constructor(props) {
36 | super(props);
37 | let { reported, uid } = props;
38 | this.state = { reported, uid };
39 | }
40 |
41 | onClick() {
42 | if (this.state.reported) {
43 | return;
44 | }
45 | vex.dialog.confirm({
46 | unsafeMessage: generateConfirmationDialog(),
47 | callback: this.onConfirm.bind(this)
48 | });
49 | }
50 |
51 | onConfirm(ok) {
52 | // If user clicked cancel, end.
53 | if (!ok) {
54 | return;
55 | }
56 |
57 | var promise = this.props.getEmailPromise();
58 | if (promise) {
59 | promise.then(this.onEmailPromiseResolved.bind(this));
60 | }
61 | }
62 |
63 | onEmailPromiseResolved(result) {
64 | this.setState({reported: true});
65 |
66 | chrome.runtime.sendMessage({
67 | method: "sendReport",
68 | reportType: "email",
69 | reportContent: result,
70 | identifier: this.state.uid,
71 | });
72 | }
73 |
74 | render() {
75 | if (this.state.reported) {
76 | return (
77 |
79 | );
80 | } else {
81 | return (
82 |
85 | );
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/components/ScanResults.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | import React from "react";
19 |
20 | function getBrandHTML(brand) {
21 | // TODO: Need to find a way to dynamically load FontAwesome icon by
22 | // brand name.
23 | if (brand.length == 0) {
24 | return "";
25 | }
26 | return brand[0].toUpperCase() + brand.slice(1).toLowerCase();;
27 | }
28 |
29 | export function ScanResultsSafelisted(props) {
30 | return (
31 |
181 | );
182 | }
183 |
--------------------------------------------------------------------------------
/src/components/WebmailLinkDialog.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | import React from "react";
19 |
20 | export default function WebmailLinkDialog(props) {
21 | return (
22 |
23 | PhishDetect
24 | {props.content}
25 | {props.href}
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/WebmailLinkWarning.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | import React from "react";
19 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
20 | import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons";
21 |
22 | export default function WebmailLinkWarning(props) {
23 | return (
24 |
26 | {" "}
27 |
28 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/src/components/WebmailWarning.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | import React from "react";
19 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
20 | import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons";
21 |
22 | export default class WebmailWarning extends React.Component {
23 | getWarningText(alertType) {
24 | switch(alertType) {
25 | case "email_sender":
26 | case "email_sender_domain":
27 | return chrome.i18n.getMessage("webmailWarningSender");
28 | case "email_link":
29 | return chrome.i18n.getMessage("webmailWarningLinks");
30 | }
31 | }
32 |
33 | render() {
34 | return (
35 |
55 | );
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/css/phishdetect-ui.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 |
3 | html, body {
4 | @apply bg-offwhite text-almostblack text-base;
5 | font-family: "Lucida Grande", "Segoe UI", Tahoma, "DejaVu Sans", Arial, sans-serif;
6 | }
7 |
8 | #container {
9 | @apply p-8;
10 | min-width: 300px;
11 | }
12 |
13 | @font-face {
14 | font-family: "Inter", sans-serif;
15 | src: url("/fonts/Inter/Inter-VariableFont_slnt,wght.ttf") format("truetype");
16 | }
17 |
18 | h1 {
19 | font-family: "Inter", sans-serif;
20 | @apply text-2xl mb-4 font-bold;
21 | }
22 | h2 {
23 | font-family: "Inter", sans-serif;
24 | @apply text-xl m-0 p-0 font-bold;
25 | }
26 | h3, h4 {
27 | font-family: "Inter", sans-serif;
28 | @apply text-lg p-0 p-0 font-bold;
29 | }
30 |
31 | a {
32 | @apply text-whaleblue underline;
33 | }
34 | a:hover {
35 | @apply text-whaleblue-light;
36 | }
37 |
38 | p {
39 | @apply leading-normal;
40 | }
41 |
42 | .pd-container {
43 | @apply container mx-auto justify-center items-center;
44 | }
45 | .pd-dialog {
46 | @apply mt-20 w-1/3 mx-auto bg-white text-black shadow-lg p-6 mb-6 rounded;
47 | }
48 | .separator {
49 | @apply border-b border-gray-500 mt-1 mb-1;
50 | }
51 |
52 | /*
53 | BUTTONS
54 | */
55 | .pd-button-blue {
56 | @apply py-2 px-4 rounded bg-whaleblue cursor-pointer text-white no-underline;
57 | }
58 | .pd-button-blue:hover {
59 | @apply bg-whaleblue-light text-white no-underline;
60 | }
61 | .pd-button-blue-outline {
62 | @apply py-2 px-4 rounded border-2 border-whaleblue cursor-pointer text-whaleblue no-underline;
63 | }
64 | .pd-button-blue-outline:hover {
65 | @apply border-whaleblue-light text-whaleblue no-underline;
66 | }
67 | .pd-button-red {
68 | @apply py-2 px-4 rounded bg-red-500 cursor-pointer text-white no-underline;
69 | }
70 | .pd-button-red:hover {
71 | @apply bg-red-600 border-red-600 no-underline;
72 | }
73 | .pd-button-gray {
74 | @apply py-2 px-4 rounded bg-gray-300 cursor-pointer text-gray-800 no-underline;
75 | }
76 | .pd-button-gray:hover {
77 | @apply bg-gray-400 border-gray-400 no-underline;
78 | }
79 | .pd-button-disabled {
80 | @apply py-2 px-4 rounded bg-gray-300 text-gray-800 no-underline;
81 | }
82 | .pd-button-disabled:hover {
83 | @apply text-gray-800;
84 | }
85 |
86 | .pd-form-input {
87 | @apply appearance-none bg-white border border-gray-300 px-2 py-1 rounded;
88 | }
89 | .pd-form-input:focus {
90 | @apply border-whaleblue;
91 | }
92 |
93 | .pd-status-red {
94 | @apply bg-red-200 border border-red-400 text-red-700 px-3 py-2 rounded relative;
95 | }
96 | .pd-status-red-text {
97 | @apply text-red-700;
98 | }
99 | .pd-status-yellow {
100 | @apply bg-yellow-200 border border-yellow-400 text-yellow-700 px-3 py-2 rounded relative;
101 | }
102 | .pd-status-yellow-text {
103 | @apply text-yellow-700;
104 | }
105 | .pd-needs-online {
106 | display: none;
107 | }
108 |
109 | .pd-yellow-warning {
110 | @apply border-l-8 border-yellow-400 mb-8 bg-yellow-200 text-yellow-700 p-6 rounded-lg leading-normal
111 | }
112 | .pd-yellow-warning .highlight {
113 | @apply font-mono bg-yellow-300 text-yellow-700;
114 | }
115 | .pd-red-warning {
116 | @apply
117 | border-l-8 border-red-400 mb-8 bg-red-200 text-red-700 p-6 rounded-lg leading-normal;
118 | }
119 | .pd-red-warning .highlight {
120 | @apply font-mono bg-red-300 text-red-700;
121 | }
122 | .pd-safelisted {
123 | @apply border-l-8 border-green-300 mb-8 bg-green-200 text-green-700 p-6 rounded-lg leading-normal;
124 | }
125 | .pd-information {
126 | @apply border-l-8 border-blue-300 mb-8 bg-blue-200 text-blue-700 p-6 rounded-lg leading-normal;
127 | }
128 |
129 | /*
130 | WARNING PAGE
131 | */
132 | .pd-blocked-body {
133 | @apply bg-almostblack h-screen;
134 | }
135 | .pd-blocked-header {
136 | background-color: #cbcbca;
137 | }
138 | .pd-blocked-content {
139 | background-image: url('/ui/warning/background.png');
140 | background-repeat: no-repeat no-repeat;
141 | background-size: 100%;
142 | @apply bg-almostblack text-white;
143 | }
144 | .pd-blocked-content-info {
145 | padding-top: 600px;
146 | padding-bottom: 100px;
147 | @apply w-1/2 mx-auto justify-center items-center text-lg;
148 | }
149 |
150 | /*
151 | SETTINGS PAGE
152 | */
153 | .pd-settings-box {
154 | @apply bg-white text-black shadow-lg p-6 mb-6 rounded;
155 | }
156 |
157 | .pd-settings-toggle:checked {
158 | @apply: right-0 border-turquoise;
159 | right: 0;
160 | border-color: #6ACEA5;
161 | }
162 | .pd-settings-toggle:checked + .pd-settings-toggle-label {
163 | @apply: bg-turquoise;
164 | background-color: #6ACEA5;
165 | }
166 |
167 | @tailwind utilities;
168 |
--------------------------------------------------------------------------------
/src/css/phishdetect-webmails.css:
--------------------------------------------------------------------------------
1 | .pd-webmail-dialog-red-button {
2 | color: #fff;
3 | background-color: #e3342f;
4 | }
5 | .pd-webmail-dialog-green-button {
6 | color: #fff;
7 | background-color: #38c172;
8 | }
9 | .pd-webmail-warning {
10 | background-color: #22292f;
11 | color: #f1f5f8;
12 | padding: 1rem;
13 | padding-top: 0;
14 | margin-bottom: 1rem;
15 | border-radius: .5rem;
16 | letter-spacing: 0;
17 | }
18 | .pd-webmail-report {
19 | padding: .5rem;
20 | border-radius: .5rem;
21 | }
22 | .pd-webmail-report:hover {
23 | background-color: #f1f5f8;
24 | }
25 | .pd-webmail-button-icon {
26 | margin-right: .5rem;
27 | }
28 | .pd-webmail-button .fa-fish {
29 | color: #3490dc;
30 | }
31 | .pd-webmail-button .fa-check-circle {
32 | color: #38c172;
33 | }
34 | .pd-webmail-link-warning {
35 | color: #e3342f;
36 | }
37 | .roundcube .pd-webmail-button {
38 | display: block;
39 | font-size: .82rem;
40 | }
41 | .pd-webmail-reported {
42 | cursor: auto;
43 | }
44 | .roundcube .pd-webmail-report.pd-webmail-button {
45 | cursor: pointer;
46 | padding: .5rem;
47 | }
48 |
49 | .pd-webmail-dialog {
50 | overflow-wrap: break-word;
51 | word-wrap: break-word;
52 | -ms-word-break: break-all;
53 | word-break: break-all;
54 | word-break: break-word;
55 | }
56 | .pd-webmail-dialog-url {
57 | font-family: Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
58 | color: #2779bd;
59 | }
60 |
--------------------------------------------------------------------------------
/src/fonts/Inter/Inter-VariableFont_slnt,wght.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/fonts/Inter/Inter-VariableFont_slnt,wght.ttf
--------------------------------------------------------------------------------
/src/fonts/Inter/OFL.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016-2019 The Inter Project Authors (me@rsms.me)
2 |
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 | This license is copied below, and is also available with a FAQ at:
5 | http://scripts.sil.org/OFL
6 |
7 |
8 | -----------------------------------------------------------
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | -----------------------------------------------------------
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide
14 | development of collaborative font projects, to support the font creation
15 | efforts of academic and linguistic communities, and to provide a free and
16 | open framework in which fonts may be shared and improved in partnership
17 | with others.
18 |
19 | The OFL allows the licensed fonts to be used, studied, modified and
20 | redistributed freely as long as they are not sold by themselves. The
21 | fonts, including any derivative works, can be bundled, embedded,
22 | redistributed and/or sold with any software provided that any reserved
23 | names are not used by derivative works. The fonts and derivatives,
24 | however, cannot be released under any other type of license. The
25 | requirement for fonts to remain under this license does not apply
26 | to any document created using the fonts or their derivatives.
27 |
28 | DEFINITIONS
29 | "Font Software" refers to the set of files released by the Copyright
30 | Holder(s) under this license and clearly marked as such. This may
31 | include source files, build scripts and documentation.
32 |
33 | "Reserved Font Name" refers to any names specified as such after the
34 | copyright statement(s).
35 |
36 | "Original Version" refers to the collection of Font Software components as
37 | distributed by the Copyright Holder(s).
38 |
39 | "Modified Version" refers to any derivative made by adding to, deleting,
40 | or substituting -- in part or in whole -- any of the components of the
41 | Original Version, by changing formats or by porting the Font Software to a
42 | new environment.
43 |
44 | "Author" refers to any designer, engineer, programmer, technical
45 | writer or other person who contributed to the Font Software.
46 |
47 | PERMISSION & CONDITIONS
48 | Permission is hereby granted, free of charge, to any person obtaining
49 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
50 | redistribute, and sell modified and unmodified copies of the Font
51 | Software, subject to the following conditions:
52 |
53 | 1) Neither the Font Software nor any of its individual components,
54 | in Original or Modified Versions, may be sold by itself.
55 |
56 | 2) Original or Modified Versions of the Font Software may be bundled,
57 | redistributed and/or sold with any software, provided that each copy
58 | contains the above copyright notice and this license. These can be
59 | included either as stand-alone text files, human-readable headers or
60 | in the appropriate machine-readable metadata fields within text or
61 | binary files as long as those fields can be easily viewed by the user.
62 |
63 | 3) No Modified Version of the Font Software may use the Reserved Font
64 | Name(s) unless explicit written permission is granted by the corresponding
65 | Copyright Holder. This restriction only applies to the primary font name as
66 | presented to the users.
67 |
68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69 | Software shall not be used to promote, endorse or advertise any
70 | Modified Version, except to acknowledge the contribution(s) of the
71 | Copyright Holder(s) and the Author(s) or with their explicit written
72 | permission.
73 |
74 | 5) The Font Software, modified or unmodified, in part or in whole,
75 | must be distributed entirely under this license, and must not be
76 | distributed under any other license. The requirement for fonts to
77 | remain under this license does not apply to any document created
78 | using the Font Software.
79 |
80 | TERMINATION
81 | This license becomes null and void if any of the above conditions are
82 | not met.
83 |
84 | DISCLAIMER
85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93 | OTHER DEALINGS IN THE FONT SOFTWARE.
94 |
--------------------------------------------------------------------------------
/src/fonts/Inter/README.txt:
--------------------------------------------------------------------------------
1 | Inter Variable Font
2 | ===================
3 |
4 | This download contains Inter as both a variable font and static fonts.
5 |
6 | Inter is a variable font with these axes:
7 | slnt
8 | wght
9 |
10 | This means all the styles are contained in a single file:
11 | Inter-VariableFont_slnt,wght.ttf
12 |
13 | If your app fully supports variable fonts, you can now pick intermediate styles
14 | that aren’t available as static fonts. Not all apps support variable fonts, and
15 | in those cases you can use the static font files for Inter:
16 | static/Inter-Thin.ttf
17 | static/Inter-ExtraLight.ttf
18 | static/Inter-Light.ttf
19 | static/Inter-Regular.ttf
20 | static/Inter-Medium.ttf
21 | static/Inter-SemiBold.ttf
22 | static/Inter-Bold.ttf
23 | static/Inter-ExtraBold.ttf
24 | static/Inter-Black.ttf
25 |
26 | Get started
27 | -----------
28 |
29 | 1. Install the font files you want to use
30 |
31 | 2. Use your app's font picker to view the font family and all the
32 | available styles
33 |
34 | Learn more about variable fonts
35 | -------------------------------
36 |
37 | https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
38 | https://variablefonts.typenetwork.com
39 | https://medium.com/variable-fonts
40 |
41 | In desktop apps
42 |
43 | https://theblog.adobe.com/can-variable-fonts-illustrator-cc
44 | https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
45 |
46 | Online
47 |
48 | https://developers.google.com/fonts/docs/getting_started
49 | https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
50 | https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
51 |
52 | Installing fonts
53 |
54 | MacOS: https://support.apple.com/en-us/HT201749
55 | Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
56 | Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
57 |
58 | Android Apps
59 |
60 | https://developers.google.com/fonts/docs/android
61 | https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
62 |
63 | License
64 | -------
65 | Please read the full license text (OFL.txt) to understand the permissions,
66 | restrictions and requirements for usage, redistribution, and modification.
67 |
68 | You can use them freely in your products & projects - print or digital,
69 | commercial or otherwise. However, you can't sell the fonts on their own.
70 |
71 | This isn't legal advice, please consider consulting a lawyer and see the full
72 | license for all details.
73 |
--------------------------------------------------------------------------------
/src/icons/128x128/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/icons/128x128/icon.png
--------------------------------------------------------------------------------
/src/icons/128x128/icon_dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/icons/128x128/icon_dot.png
--------------------------------------------------------------------------------
/src/icons/128x128/icon_error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/icons/128x128/icon_error.png
--------------------------------------------------------------------------------
/src/icons/128x128/icon_gray.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/icons/128x128/icon_gray.png
--------------------------------------------------------------------------------
/src/icons/128x128/icon_green.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/icons/128x128/icon_green.png
--------------------------------------------------------------------------------
/src/icons/128x128/icon_green_dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/icons/128x128/icon_green_dot.png
--------------------------------------------------------------------------------
/src/icons/16x16/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/icons/16x16/icon.png
--------------------------------------------------------------------------------
/src/icons/16x16/icon_dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/icons/16x16/icon_dot.png
--------------------------------------------------------------------------------
/src/icons/16x16/icon_error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/icons/16x16/icon_error.png
--------------------------------------------------------------------------------
/src/icons/16x16/icon_gray.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/icons/16x16/icon_gray.png
--------------------------------------------------------------------------------
/src/icons/16x16/icon_green.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/icons/16x16/icon_green.png
--------------------------------------------------------------------------------
/src/icons/16x16/icon_green_dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/icons/16x16/icon_green_dot.png
--------------------------------------------------------------------------------
/src/icons/32x32/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/icons/32x32/icon.png
--------------------------------------------------------------------------------
/src/icons/32x32/icon_dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/icons/32x32/icon_dot.png
--------------------------------------------------------------------------------
/src/icons/32x32/icon_error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/icons/32x32/icon_error.png
--------------------------------------------------------------------------------
/src/icons/32x32/icon_gray.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/icons/32x32/icon_gray.png
--------------------------------------------------------------------------------
/src/icons/32x32/icon_green.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/icons/32x32/icon_green.png
--------------------------------------------------------------------------------
/src/icons/32x32/icon_green_dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/icons/32x32/icon_green_dot.png
--------------------------------------------------------------------------------
/src/icons/48x48/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/icons/48x48/icon.png
--------------------------------------------------------------------------------
/src/icons/48x48/icon_dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/icons/48x48/icon_dot.png
--------------------------------------------------------------------------------
/src/icons/48x48/icon_error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/icons/48x48/icon_error.png
--------------------------------------------------------------------------------
/src/icons/48x48/icon_gray.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/icons/48x48/icon_gray.png
--------------------------------------------------------------------------------
/src/icons/48x48/icon_green.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/icons/48x48/icon_green.png
--------------------------------------------------------------------------------
/src/icons/48x48/icon_green_dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/icons/48x48/icon_green_dot.png
--------------------------------------------------------------------------------
/src/images/email/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/images/email/banner.png
--------------------------------------------------------------------------------
/src/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/images/logo.png
--------------------------------------------------------------------------------
/src/images/school_of_phish.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/images/school_of_phish.png
--------------------------------------------------------------------------------
/src/js/alarms.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | chrome.alarms.onAlarm.addListener(function(alarm) {
19 | if (alarm.name == "alarmUpdateIndicators") {
20 | updateIndicators();
21 | } else if (alarm.name == "alarmUpdateConfig") {
22 | // This will update the configuration in case the server config
23 | // changes and it would no longer require an API key.
24 | cfg.fetchNodeConfig(setStatusOnline, setStatusOffline);
25 | }
26 | });
27 |
28 | chrome.alarms.create("alarmUpdateIndicators", {periodInMinutes: INDICATORS_UPDATE_FREQUENCY});
29 | chrome.alarms.create("alarmUpdateConfig", {periodInMinutes: CONFIG_UPDATE_FREQUENCY});
30 |
--------------------------------------------------------------------------------
/src/js/alerts.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | function sendAlert(alertType, match, indicator, identifier) {
19 | if (cfg.getSendAlerts() === false) {
20 | return;
21 | }
22 |
23 | // Check if the alert type is email_*.
24 | if (alertType.startsWith("email_")) {
25 | // If an email identifier was provided...
26 | if (identifier !== undefined && identifier != "") {
27 | // Get a list of already reported emails.
28 | const emails = cfg.getDetectedEmails();
29 | for (let i=0; i response.json())
54 | .then(function(data) {
55 | // If alert is of type email_* we add the email ID to the list of
56 | // successfully reported emails.
57 | if (alertType.startsWith("email_")) {
58 | if (identifier !== undefined && identifier != "") {
59 | cfg.addDetectedEmail(identifier);
60 | }
61 | }
62 |
63 | console.log("Sent notification", alertType, "to PhishDetect Node.");
64 | })
65 | .catch(error => {
66 | console.log(error);
67 | });
68 | }
69 |
--------------------------------------------------------------------------------
/src/js/background.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | // Check for web requests.
19 | chrome.webRequest.onBeforeRequest.addListener(function(details) {
20 | // TODO: Should we check for main_frame only?
21 | // if (details.type != "main_frame") {
22 | // return {cancel: false};
23 | // }
24 |
25 | // We lowercase the link.
26 | const url = details.url.toLowerCase();
27 |
28 | const domain = window.getDomainFromURL(url);
29 | const topDomain = window.getTopDomainFromURL(url);
30 |
31 | if (domain === null || topDomain === null) {
32 | return {cancel: false};
33 | }
34 |
35 | const domainHash = sha256(domain);
36 | const topDomainHash = sha256(topDomain);
37 |
38 | const indicators = cfg.getIndicators();
39 | if (indicators === undefined || indicators.domains === undefined || indicators.domains === null) {
40 | return {cancel: false};
41 | }
42 |
43 | const itemsToCheck = [domainHash, topDomainHash];
44 | const matchedIndicator = checkForIndicators(itemsToCheck, indicators.domains);
45 | if (matchedIndicator !== null) {
46 | console.log("Bad domain identified:", url);
47 | sendAlert("website_visit", url, matchedIndicator, "");
48 |
49 | // We redirect to the warning page.
50 | const redirect = chrome.extension.getURL(WARNING_PAGE) + "?url=" + encodeURIComponent(url) + "&indicator=" + matchedIndicator;
51 | return {redirectUrl: redirect};
52 | }
53 |
54 | // If nothing suspicious is found, proceed with visit.
55 | return {cancel: false};
56 | },
57 | {urls: [""]},
58 | ["blocking"]
59 | );
60 |
61 | chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
62 | switch (request.method) {
63 | //=========================================================================
64 | // Messages related to internal background script functioning.
65 | //=========================================================================
66 | case "updateNode":
67 | console.log("New node set in UI");
68 | cfg.setNode(request.node, request.key);
69 | break;
70 | case "updateIndicators":
71 | updateIndicators(request.full_update);
72 | break;
73 | case "getStatus":
74 | sendResponse(cfg.status);
75 | break;
76 |
77 | //=========================================================================
78 | // Messages related to indicators.
79 | //=========================================================================
80 | // This message is received when a component of the extension is requesting the
81 | // full list of indicators.
82 | case "getIndicators":
83 | sendResponse(cfg.getIndicators());
84 | break;
85 |
86 | //=========================================================================
87 | // Messages related to link and page analysis.
88 | //=========================================================================
89 | // This message is received when the user requests to scan an opened page.
90 | case "scanPage":
91 | scanPage(request.tabId, request.tabUrl);
92 | break;
93 | case "scanLink":
94 | scanLink(request.link);
95 | break;
96 | case "scanHistory":
97 | scanBrowsingHistory(request.tabId);
98 | break;
99 |
100 | //=========================================================================
101 | // Messages sending data to the Node.
102 | //=========================================================================
103 | // This message is received when an alert needs to be sent to the PhishDetect node.
104 | case "sendAlert":
105 | // Send alert to REST API server.
106 | sendAlert(request.alertType, request.match, request.indicator, request.identifier);
107 | break;
108 | case "sendReport":
109 | sendReport(request.reportType, request.reportContent, request.identifier);
110 | break;
111 | case "sendReview":
112 | sendReview(request.ioc, request.tabId);
113 | break;
114 |
115 | //=========================================================================
116 | // Messages requesting configuration options.
117 | //=========================================================================
118 | // Get the flag to enable or disable webmails integration.
119 | case "getWebmailsIntegration":
120 | sendResponse(cfg.getWebmailsIntegration());
121 | break;
122 | case "getNodeEnableAnalysis":
123 | sendResponse(cfg.getNodeEnableAnalysis());
124 | break;
125 | case "loadConfiguration":
126 | sendResponse({config: cfg.config, status: cfg.status});
127 | break;
128 | case "updateConfiguration":
129 | sendResponse(cfg.updateConfig(request.config));
130 | break;
131 |
132 | //=========================================================================
133 | // Extras
134 | //=========================================================================
135 | // This message returns the list of email IDs that were already previously shared.
136 | case "getReportedEmails":
137 | sendResponse(cfg.getReportedEmails());
138 | break;
139 | }
140 |
141 | return false;
142 | });
143 |
144 | // Context menus.
145 | function loadContextMenus() {
146 | chrome.contextMenus.removeAll(function() {
147 | chrome.contextMenus.create({
148 | "title": chrome.i18n.getMessage("contextMenuReportPage"),
149 | "id": "report-page",
150 | "contexts": ["page", "frame"]
151 | });
152 | chrome.contextMenus.create({
153 | "title": chrome.i18n.getMessage("contextMenuReportLink"),
154 | "id": "report-link",
155 | "contexts": ["link"]
156 | });
157 |
158 | if (cfg.getNodeEnableAnalysis() === true) {
159 | chrome.contextMenus.create({
160 | "title": chrome.i18n.getMessage("contextMenuScanPage"),
161 | "id": "scan-page",
162 | "contexts": ["page", "frame"]
163 | });
164 | chrome.contextMenus.create({
165 | "title": chrome.i18n.getMessage("contextMenuScanLink"),
166 | "id": "scan-link",
167 | "contexts": ["link"]
168 | });
169 | }
170 | });
171 | }
172 |
173 | chrome.contextMenus.onClicked.addListener(function(info, tab) {
174 | switch (info.menuItemId) {
175 | case "scan-page":
176 | scanPage(tab.id, tab.url);
177 | break;
178 | case "scan-link":
179 | scanLink(info.linkUrl);
180 | break;
181 | case "report-page":
182 | sendReport("url", info.pageUrl, null);
183 | break;
184 | case "report-link":
185 | sendReport("url", info.linkUrl, null);
186 | break;
187 | }
188 |
189 | return false;
190 | });
191 |
192 | chrome.runtime.onInstalled.addListener(loadContextMenus);
193 | chrome.runtime.onStartup.addListener(loadContextMenus);
194 |
195 | // Open welcome page at installation.
196 | // TODO: do we want to open one at upgrade too?
197 | chrome.runtime.onInstalled.addListener(function(details) {
198 | // "install" or "upgrade".
199 | if (details.reason === "install") {
200 | window.open("/ui/options/options.html");
201 | }
202 | });
203 |
--------------------------------------------------------------------------------
/src/js/config.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | class Config {
19 |
20 | constructor() {
21 | this.config = {};
22 | this.indicators = {};
23 | // Global status for the API server reachablity and auth.
24 | this.status = null;
25 | }
26 |
27 | //=========================================================================
28 | // Storage
29 | //=========================================================================
30 | initStorage(configCallback) {
31 | console.log("Initializing storage...");
32 |
33 | const config_defaults = {
34 | cfg_node: NODE_DEFAULT_URL,
35 | cfg_api_key: "",
36 | cfg_node_enforce_user_auth: false,
37 | cfg_node_enable_analysis: false,
38 | cfg_node_contacts: "",
39 | cfg_last_update: null,
40 | cfg_send_alerts: true,
41 | cfg_webmails: true,
42 | cfg_contact: "",
43 | cfg_detected_emails: [],
44 | cfg_reported_emails: [],
45 | cfg_last_error: null,
46 | };
47 |
48 | var config_options = {};
49 |
50 | // If localStorage settings exist, use defaults to iterate through
51 | // existing options, and retrieve the settings from localStorage.
52 | if (localStorage.getItem("cfg_node") !== null) {
53 | config_options = this.migrateLocalStorage(config_defaults);
54 | } else {
55 | // Otherwise we just deep copy the default settings.
56 | for (const [default_key, default_value] of Object.entries(config_defaults)) {
57 | config_options[default_key] = default_value;
58 | }
59 | }
60 |
61 | chrome.storage.sync.get({config: {}}, result => {
62 | for (const [config_key, config_value] of Object.entries(config_options)) {
63 | if (result["config"][config_key] == undefined) {
64 | result["config"][config_key] = config_value;
65 | }
66 | }
67 |
68 | // Store the config values on the Config object.
69 | this.config = result["config"];
70 | // Persist config with storage API.
71 | chrome.storage.sync.set({config: this.config});
72 |
73 | console.log("Storage initialization completed.");
74 |
75 | // Load indicators with async API.
76 | return chrome.storage.local.get({indicators: {domains: [], emails: [],}}, (result) => {
77 | console.log("Loaded indicators from storage.");
78 | this.indicators = result.indicators;
79 | configCallback();
80 | });
81 | });
82 | }
83 |
84 | migrateLocalStorage(config_defaults) {
85 | console.log("Migrating configuration from localStorage...");
86 |
87 | var config_options = {};
88 | // Migrate config from localstorage to storage API
89 | for (const config_key in config_defaults) {
90 | const existing_value = localStorage.getItem(config_key);
91 | if (existing_value != undefined) {
92 | config_options[config_key] = existing_value;
93 | }
94 | }
95 |
96 | // Reset last update to force a full indicator update after extension
97 | // upgrade.
98 | config_options.cfg_last_update = null;
99 |
100 | // Clear localStorage as it is not used anymore.
101 | localStorage.clear();
102 |
103 | return config_options;
104 | }
105 |
106 | getItem(item_name){
107 | // Retrieve a config value from the config object.
108 | return this.config[item_name];
109 | }
110 |
111 | setItem(item_name, item_value) {
112 | // Update a config value and persist to storage.
113 | this.config[item_name] = item_value;
114 | chrome.storage.sync.set({config: this.config});
115 | }
116 |
117 | loadFromBackground(config_loaded_callback) {
118 | // Lightweight method to populate Config data from the background page
119 | // for use in the UI.
120 | chrome.runtime.sendMessage({method: "loadConfiguration"}, function(response) {
121 | // Got config state from background page
122 | cfg.config = response.config;
123 | cfg.status = response.status;
124 | config_loaded_callback();
125 | });
126 | }
127 |
128 | updateConfig(config) {
129 | // Reload background page config and persist to storage object when
130 | // changes made from UI.
131 | this.config = config;
132 | chrome.storage.sync.set({config: this.config});
133 | }
134 |
135 | clearStorage() {
136 | this.config = {};
137 | this.indicators = {};
138 | chrome.storage.sync.clear();
139 | chrome.storage.local.clear();
140 | // Clear legacy localStorage if it still exist.
141 | localStorage.clear();
142 | }
143 |
144 | //=========================================================================
145 | // Node
146 | //=========================================================================
147 | getNode() {
148 | const address = this.getItem("cfg_node");
149 | if (address && !address.startsWith("http")) {
150 | return "https://" + address;
151 | }
152 | return address;
153 | }
154 |
155 | setNode(node, api_key) {
156 | const currentAddress = this.getNode();
157 | if (node == currentAddress) {
158 | return;
159 | }
160 |
161 | console.log("Attention! Reconfiguring PhishDetect Node...");
162 |
163 | // If we are changing the server, we also reset all the storage.
164 | this.clearStorage();
165 | // We reinitialize the storage.
166 | this.initStorage(() => {
167 | // Then we set the new node.
168 | this.setItem("cfg_node", node);
169 | if (api_key) {
170 | this.setApiKey(api_key);
171 | }
172 | // And we pull the new config.
173 | this.fetchNodeConfig(initSuccess, initFailure);
174 | });
175 | }
176 |
177 | restoreDefaultNode() {
178 | this.setNode(NODE_DEFAULT_URL);
179 | }
180 |
181 | //=========================================================================
182 | // API Keys
183 | //=========================================================================
184 | getApiKey() {
185 | return this.getItem("cfg_api_key");
186 | }
187 |
188 | setApiKey(value) {
189 | this.setItem("cfg_api_key", value);
190 | }
191 |
192 | //=========================================================================
193 | // Node configuration
194 | //=========================================================================
195 | fetchNodeConfig(success, failure) {
196 | console.log("Fetching configuration from Node...");
197 |
198 | let url = this.getAPIConfigURL();
199 |
200 | fetch(url)
201 | .then((response) => response.json())
202 | .then(data => {
203 | this.setNodeAdminName(data.admin_name);
204 | this.setNodeAdminContacts(data.admin_contacts);
205 | this.setNodeEnableAnalysis(data.enable_analysis);
206 | this.setNodeEnforceUserAuth(data.enforce_user_auth);
207 |
208 | console.log("Configuration fetched successfully.");
209 | // Calling success callback if all worked.
210 | success();
211 | })
212 | .catch(error => {
213 | console.log("ERROR: Connection failed: " + error);
214 | // Calling failure callback if errored.
215 | failure();
216 | });
217 | }
218 |
219 | getNodeAdminName() {
220 | return this.getItem("cfg_node_admin_name");
221 | }
222 | setNodeAdminName(value) {
223 | this.setItem("cfg_node_admin_name", value);
224 | }
225 |
226 | getNodeAdminContacts() {
227 | return this.getItem("cfg_node_admin_contacts");
228 | }
229 | setNodeAdminContacts(value) {
230 | this.setItem("cfg_node_admin_contacts", value);
231 | }
232 |
233 | getNodeEnableAnalysis() {
234 | return this.getItem("cfg_node_enable_analysis");
235 | }
236 |
237 | setNodeEnableAnalysis(value) {
238 | this.setItem("cfg_node_enable_analysis", value);
239 | }
240 |
241 | getNodeEnforceUserAuth() {
242 | return this.getItem("cfg_node_enforce_user_auth");
243 | }
244 |
245 | setNodeEnforceUserAuth(value) {
246 | this.setItem("cfg_node_enforce_user_auth", value);
247 | }
248 |
249 | //=========================================================================
250 | // Node URLs
251 | //=========================================================================
252 | addAuthToURL(url) {
253 | if (this.getNodeEnforceUserAuth() == true) {
254 | url += "?key=" + this.getApiKey();
255 | }
256 | return url;
257 | }
258 |
259 | getAPIConfigURL() {
260 | return new URL(NODE_API_CONFIG_PATH, this.getNode()).href;
261 | }
262 |
263 | getAPIRegisterURL() {
264 | return new URL(NODE_API_REGISTER_PATH, this.getNode()).href;
265 | }
266 |
267 | getAPIAuthURL() {
268 | let url = new URL(NODE_API_AUTH_PATH, this.getNode()).href;
269 | return this.addAuthToURL(url);
270 | }
271 |
272 | getAPIAnalyzeHTMLURL() {
273 | let url = new URL(NODE_API_ANALYZE_HTML_PATH, this.getNode()).href;
274 | return this.addAuthToURL(url);
275 | }
276 |
277 | getAPIAnalyzeLinkURL() {
278 | let url = new URL(NODE_API_ANALYZE_LINK_PATH, this.getNode()).href;
279 | return this.addAuthToURL(url);
280 | }
281 |
282 | getAPIIndicatorsURL() {
283 | let url = new URL(NODE_API_INDICATORS_FETCH_PATH, this.getNode()).href;
284 | return this.addAuthToURL(url);
285 | }
286 |
287 | getAPIRecentIndicatorsURL() {
288 | let url = new URL(NODE_API_RECENT_INDICATORS_FETCH_PATH, this.getNode()).href;
289 | return this.addAuthToURL(url);
290 | }
291 |
292 | getAPIAlertsAddURL() {
293 | let url = new URL(NODE_API_ALERTS_ADD_PATH, this.getNode()).href;
294 | return this.addAuthToURL(url);
295 | }
296 |
297 | getAPIReportsAddURL() {
298 | let url = new URL(NODE_API_REPORTS_ADD_PATH, this.getNode()).href;
299 | return this.addAuthToURL(url);
300 | }
301 |
302 | getAPIReviewsAddURL() {
303 | let url = new URL(NODE_API_REVIEWS_ADD_PATH, this.getNode()).href;
304 | return this.addAuthToURL(url);
305 | }
306 |
307 | //=========================================================================
308 | // Send alerts.
309 | //=========================================================================
310 | getSendAlerts() {
311 | return this.getItem("cfg_send_alerts");
312 | }
313 |
314 | setSentAlerts(value) {
315 | this.setItem("cfg_send_alerts", value);
316 | }
317 |
318 | //=========================================================================
319 | // Webmails integration.
320 | //=========================================================================
321 | getWebmailsIntegration() {
322 | return this.getItem("cfg_webmails");
323 | }
324 |
325 | setWebmailsIntegration(value) {
326 | this.setItem("cfg_webmails", value);
327 | }
328 |
329 | //=========================================================================
330 | // Indicators
331 | //=========================================================================
332 | getIndicators() {
333 | return this.indicators;
334 | }
335 |
336 | setIndicators(value, callback) {
337 | // If the domains and emails are undefined, something went wrong.
338 | if (value.domains === undefined || value.emails === undefined) {
339 | console.error("Indicator list missing the domain or email field.");
340 | return;
341 | }
342 |
343 | this.indicators = value;
344 | return chrome.storage.local.set({indicators: value}, callback);
345 | }
346 |
347 | //=========================================================================
348 | // Update time
349 | //=========================================================================
350 | getLastFullUpdateTime() {
351 | const lastUpdate = this.getItem("cfg_last_update");
352 | if (lastUpdate == null) {
353 | return null;
354 | }
355 |
356 | return lastUpdate;
357 | }
358 |
359 | setLastFullUpdateTime() {
360 | this.setItem("cfg_last_update", getCurrentUTCDate());
361 | }
362 |
363 | //=========================================================================
364 | // User contact details
365 | //=========================================================================
366 | getUserContact() {
367 | return this.getItem("cfg_contact");
368 | }
369 |
370 | setContact(value) {
371 | this.setItem("cfg_contact", value);
372 | }
373 |
374 | //=========================================================================
375 | // Emails that were detected in webmail
376 | //=========================================================================
377 | getDetectedEmails() {
378 | return this.getItem("cfg_detected_emails");
379 | }
380 |
381 | addDetectedEmail(value) {
382 | let emails = this.getDetectedEmails();
383 | emails.push(value);
384 | this.setItem("cfg_detected_emails", emails);
385 | }
386 |
387 | //=========================================================================
388 | // Emails that were reported by the user to the Node
389 | //=========================================================================
390 | getReportedEmails() {
391 | return this.getItem("cfg_reported_emails");
392 | }
393 |
394 | addReportedEmail(value) {
395 | let emails = this.getReportedEmails();
396 | emails.push(value);
397 | this.setItem("cfg_reported_emails", emails);
398 | }
399 | }
400 |
401 | const cfg = new Config();
402 |
--------------------------------------------------------------------------------
/src/js/const.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | NODE_DEFAULT_URL = "https://phishdetect.securitywithoutborders.org";
19 |
20 | NODE_API_CONFIG_PATH = "/api/config/";
21 | NODE_API_INDICATORS_FETCH_PATH = "/api/indicators/fetch/";
22 | NODE_API_RECENT_INDICATORS_FETCH_PATH = "/api/indicators/fetch/recent/";
23 | NODE_API_ALERTS_ADD_PATH = "/api/alerts/add/";
24 | NODE_API_REPORTS_ADD_PATH = "/api/reports/add/";
25 | NODE_API_AUTH_PATH = "/api/auth/";
26 | NODE_API_ANALYZE_HTML_PATH = "/api/analyze/html/";
27 | NODE_API_ANALYZE_LINK_PATH = "/api/analyze/link/";
28 | NODE_API_REGISTER_PATH = "/api/users/register/";
29 | NODE_API_REVIEWS_ADD_PATH = "/api/reviews/add/";
30 |
31 | WARNING_PAGE = "ui/warning/warning.html";
32 | REPORT_PAGE = "ui/report/report.html";
33 | REPORT_FAILED_PAGE = "ui/report/report_failed.html";
34 | REVIEW_PAGE = "ui/review/review.html";
35 | REVIEW_FAILED_PAGE = "ui/review/review_failed.html";
36 | SCAN_PAGE = "ui/scan/scan.html";
37 |
38 | INDICATORS_UPDATE_FREQUENCY = 30;
39 | CONFIG_UPDATE_FREQUENCY = 60;
40 | ONE_DAY_TIME = 24 * 60 * 60 * 1000;
41 |
--------------------------------------------------------------------------------
/src/js/domains.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | "use strict";
19 |
20 | const tldts = require("tldts");
21 |
22 | window.getDomainFromURL = function getDomainFromURL(url) {
23 | const urlParsed = tldts.parse(url);
24 | return urlParsed.hostname;
25 | };
26 |
27 | window.getTopDomainFromURL = function getTopDomainFromURL(url) {
28 | const urlParsed = tldts.parse(url);
29 | return urlParsed.domain;
30 | };
31 |
--------------------------------------------------------------------------------
/src/js/getTabHTML.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | // @author Rob W
19 | // Demo: var serialized_html = DOMtoString(document);
20 |
21 | function DOMtoString(document_root) {
22 | var html = "",
23 | node = document_root.firstChild;
24 | while (node) {
25 | switch (node.nodeType) {
26 | case Node.ELEMENT_NODE:
27 | html += node.outerHTML;
28 | break;
29 | case Node.TEXT_NODE:
30 | html += node.nodeValue;
31 | break;
32 | case Node.CDATA_SECTION_NODE:
33 | html += "";
34 | break;
35 | case Node.COMMENT_NODE:
36 | html += "";
37 | break;
38 | case Node.DOCUMENT_TYPE_NODE:
39 | // (X)HTML documents are identified by public identifiers
40 | html += "\n";
41 | break;
42 | }
43 | node = node.nextSibling;
44 | }
45 | return html;
46 | }
47 |
48 | console.log("[PhishDetect] Loaded getTabHTML");
49 |
50 | chrome.runtime.onMessage.addListener(
51 | function(request, sender, sendResponse) {
52 | if (request.method && (request.method == "getTabHTML")) {
53 | console.log("[PhishDetect] Received request to extract DOM and send it for analysis...");
54 | sendResponse(DOMtoString(document));
55 | }
56 | }
57 | );
58 |
--------------------------------------------------------------------------------
/src/js/gmail.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | "use strict";
19 |
20 | // gmail.js
21 | const GmailFactory = require("gmail-js");
22 | const gmail = new GmailFactory.Gmail($);
23 | window.gmail = gmail;
24 |
25 | import scanEmail from "./scanEmail.js";
26 |
27 | function gmailCheckEmail(uid) {
28 | console.log("[PhishDetect] Checking email", uid);
29 |
30 | // Extract the email DOM.
31 | const email = new gmail.dom.email(uid);
32 | const emailBody = email.dom("body");
33 | // Extract from field and prepare hashes.
34 | const from = email.from();
35 | const fromEmail = from["email"].toLowerCase();
36 |
37 | return scanEmail(fromEmail, emailBody, uid);
38 | }
39 |
40 | // gmailModifyEmail will modify the email body and rewrite links to open our
41 | // confirmation dialog first.
42 | function gmailModifyEmail(id) {
43 | console.log("[PhishDetect] Modifying email", id);
44 |
45 | const email = new gmail.dom.email(id);
46 | const emailBody = email.dom("body");
47 | const anchors = $(emailBody).find("a, area");
48 |
49 | chrome.runtime.sendMessage({method: "getNodeEnableAnalysis"}, function(response) {
50 | for (let i=0; i").get(0);
85 | generateReportEmailButton(element, {
86 | uid: uid,
87 | reported: isReported,
88 | getEmailPromise: function() {
89 | return gmail.get.email_source_promise(uid);
90 | }
91 | });
92 | gmail.tools.add_toolbar_button(element);
93 | });
94 | }
95 |
96 | (function() {
97 | console.log("[PhishDetect] Calling PhishDetect Gmail integration...");
98 |
99 | // Check if the option to integrate with webmails is enabled.
100 | chrome.runtime.sendMessage({method: "getWebmailsIntegration"}, function(response) {
101 | if (response === false) {
102 | return;
103 | }
104 |
105 | console.log("[PhishDetect] Integrating in Gmail...");
106 |
107 | gmail.observe.on("view_email", function(obj) {
108 | console.log("[PhishDetect] Email opened with ID", obj.id);
109 |
110 | // Add report email button.
111 | gmailReportEmail(obj.id);
112 | // We check the original content of the email for known indicators.
113 | gmailCheckEmail(obj.id);
114 | // We change the email to add our dialog.
115 | gmailModifyEmail(obj.id);
116 | });
117 | });
118 | })();
119 |
--------------------------------------------------------------------------------
/src/js/gui.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | "use strict";
19 |
20 | const vex = require("vex-js");
21 | window.vex = vex;
22 | vex.registerPlugin(require("vex-dialog"));
23 | vex.defaultOptions.className = "vex-theme-default";
24 |
25 | import React from "react";
26 | import ReactDOM from "react-dom";
27 | import { renderToString } from "react-dom/server";
28 | import WebmailWarning from "../components/WebmailWarning";
29 | import WebmailLinkWarning from "../components/WebmailLinkWarning";
30 | import WebmailLinkDialog from "../components/WebmailLinkDialog";
31 | import ReportEmailButton from "../components/ReportEmailButton";
32 | import ConfirmationDialog from "../components/ConfirmationDialog";
33 |
34 | // A helper to render HTML strings from React components
35 | function renderHTML(component, options) {
36 | return renderToString(React.createElement(component, options));
37 | }
38 | // A helper to render a React component directly into a DOM element
39 | // This includes attaching any behaviors defined by the component
40 | function renderDOM(component, options, element) {
41 | ReactDOM.render(React.createElement(component, options), element);
42 | }
43 |
44 | //=============================================================================
45 | // Report email button.
46 | //=============================================================================
47 | // Renders a button into an existing DOM element and attachs a click handler
48 | // element: the container DOM element
49 | // getEmailPromise: a function that returns a promise resolving to an email
50 | // uid: the unique identifier for the email
51 | window.generateReportEmailButton = function(element, options) {
52 | return renderDOM(ReportEmailButton, options, element);
53 | };
54 | window.generateConfirmationDialog = function() {
55 | return renderHTML(ConfirmationDialog);
56 | };
57 |
58 | //=============================================================================
59 | // Email warnings when detected as malicious.
60 | //=============================================================================
61 | // generateWebmailWarning is a helper function used to generate the HTML
62 | // needed to show a warning message inside the supported webmails.
63 | window.generateWebmailWarning = function generateWebmailWarning(alertType) {
64 | return renderHTML(WebmailWarning, {alertType: alertType});
65 | };
66 | // generateWebmailLinkWarning appends a red warning sign to an HTML element
67 | // (normally a link) to alert the user that what's contained is malicious.
68 | window.generateWebmailLinkWarning = function generateWebmailLinkWarning(element) {
69 | element.insertAdjacentHTML("afterend", renderHTML(WebmailLinkWarning));
70 | };
71 |
72 | //=============================================================================
73 | // Webmail links full dialog.
74 | // This is used when the configured Node supports dynamic analysis.
75 | //=============================================================================
76 | function createDialogOnClick(href) {
77 | // We safely render the link and preview it in the dialog.
78 | const message = renderHTML(WebmailLinkDialog, {
79 | content: chrome.i18n.getMessage("webmailDialog"),
80 | href: href
81 | });
82 |
83 | // We spawn a dialog.
84 | vex.defaultOptions.contentClassName = "w-full";
85 | vex.dialog.open({
86 | unsafeMessage: message,
87 | buttons: [
88 | // Button to open "Safely".
89 | $.extend({}, vex.dialog.buttons.YES, {
90 | text: chrome.i18n.getMessage("webmailDialogSafely"),
91 | className: "pd-webmail-dialog-green-button",
92 | click: function($vexContent, event) {
93 | this.value = "safe";
94 | this.close();
95 | return false;
96 | }
97 | }),
98 | // Button to open "Directly" / "Unsafely".
99 | $.extend({}, vex.dialog.buttons.YES, {
100 | text: chrome.i18n.getMessage("webmailDialogDirectly"),
101 | className: "pd-webmail-dialog-red-button",
102 | click: function($vexContent, event) {
103 | this.value = "unsafe";
104 | this.close();
105 | return false;
106 | }
107 | }),
108 | // Button to open help page.
109 | $.extend({}, vex.dialog.buttons.YES, {
110 | text: "?",
111 | click: function($vexContent, event) {
112 | this.value = "help";
113 | return false;
114 | }
115 | })
116 | ],
117 | // Callback to handle button actions.
118 | callback: function(value) {
119 | if (value) {
120 | // Open the URL through our service.
121 | if (value == "safe") {
122 | chrome.runtime.sendMessage({method: "scanLink", link: href});
123 | // Open the URL directly.
124 | } else if (value == "unsafe") {
125 | window.open(href);
126 | } else if (value == "help") {
127 | window.open("https://phishdetect.io/help/");
128 | }
129 | }
130 | }
131 | });
132 | }
133 |
134 | // generateWebmailDialog adds a click event handler to the given anchor
135 | // in order to display a dialog offering to open the link safely.
136 | window.generateWebmailDialog = function generateWebmailDialog(anchor) {
137 | const href = anchor.href;
138 |
139 | // First we check if the href link is actually a string.
140 | if (typeof href !== "string") {
141 | return;
142 | }
143 |
144 | // We check if it is an http link.
145 | if (href.indexOf("http://") != 0 && href.indexOf("https://") != 0) {
146 | return;
147 | }
148 |
149 | // We delete data-saferedirecturl.
150 | // Maybe we should make this optional, but the value of it seems
151 | // mostly duplicated by using phishdetect.io anyway.
152 | anchor.removeAttribute("data-saferedirecturl");
153 |
154 | // We add listeners so we can catch the both left button and middle
155 | // button clicks.
156 | anchor.addEventListener("click", function(event) {
157 | event.preventDefault();
158 | createDialogOnClick(href);
159 | });
160 | anchor.addEventListener("auxclick", function(event) {
161 | if (event.button == 1) {
162 | event.preventDefault();
163 | createDialogOnClick(href);
164 | }
165 | });
166 | };
167 |
168 | //=============================================================================
169 | // Webmail links previews.
170 | // This is used when the configured Node does not support dynamic link
171 | // analysis.
172 | //=============================================================================
173 | function createPreviewOnClick(href) {
174 | // Get URLs.
175 | // const unsafeUrl = event.srcElement.getAttribute("href");
176 | const unsafeUrl = href;
177 | // We sanitize the link and preview it in the dialog.
178 | const message = renderHTML(WebmailLinkDialog, {
179 | content: chrome.i18n.getMessage("webmailPreview"),
180 | href: href
181 | });
182 |
183 | // We spawn a dialog.
184 | vex.defaultOptions.contentClassName = "w-full";
185 | vex.dialog.open({
186 | unsafeMessage: message,
187 | buttons: [
188 | $.extend({}, vex.dialog.buttons.YES, {
189 | text: chrome.i18n.getMessage("webmailPreviewContinue"),
190 | className: "pd-webmail-dialog-red-button",
191 | click: function($vexContent, event) {
192 | this.value = "continue";
193 | this.close();
194 | return false;
195 | }
196 | }),
197 | $.extend({}, vex.dialog.buttons.NO, {
198 | text: chrome.i18n.getMessage("webmailPreviewCancel"),
199 | click: function($vexContent, event) {
200 | this.close();
201 | return false;
202 | }
203 | }),
204 | // Button to open help page.
205 | $.extend({}, vex.dialog.buttons.YES, {
206 | text: "?",
207 | click: function($vexContent, event) {
208 | this.value = "help";
209 | return false;
210 | }
211 | })
212 | ],
213 | // Callback to handle button actions.
214 | callback: function(value) {
215 | if (value) {
216 | if (value == "continue") {
217 | window.open(unsafeUrl);
218 | } else if (value == "help") {
219 | window.open("https://phishdetect.io/help/");
220 | }
221 | }
222 | }
223 | });
224 | }
225 |
226 | // Similarly to generateWebmailDialog, generateWebmailPreview creates a
227 | // click event handler. However, it does not provide any option to scan
228 | // the link. This is utilized if the Node has disabled analysis.
229 | window.generateWebmailPreview = function generateWebmailPreview(anchor) {
230 | const href = anchor.href;
231 |
232 | // First we check if the href link is actually a string.
233 | if (typeof href !== "string") {
234 | return;
235 | }
236 |
237 | // We check if it is an http link.
238 | if (href.indexOf("http://") != 0 && href.indexOf("https://") != 0) {
239 | return;
240 | }
241 |
242 | // We delete data-saferedirecturl.
243 | // Maybe we should make this optional, but the value of it seems
244 | // mostly duplicated by using phishdetect.io anyway.
245 | anchor.removeAttribute("data-saferedirecturl");
246 |
247 | // We add listeners so we can catch the both left button and middle
248 | // button clicks.
249 | anchor.addEventListener("click", function(event) {
250 | event.preventDefault();
251 | createPreviewOnClick(href);
252 | });
253 | anchor.addEventListener("auxclick", function(event) {
254 | if (event.button == 1) {
255 | event.preventDefault();
256 | createPreviewOnClick(href);
257 | }
258 | });
259 | };
260 |
--------------------------------------------------------------------------------
/src/js/i18n.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | $("[data-resource]").each(function() {
19 | let el = $(this);
20 | let resourceName = el.data("resource");
21 | let resourceText = chrome.i18n.getMessage(resourceName);
22 | el.text(resourceText);
23 | });
24 |
--------------------------------------------------------------------------------
/src/js/indicators.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | function updateIndicators(full = false) {
19 | if (!checkKeySetIfNeeded()) {
20 | console.log("Node enforces user authentication and no API key is provided. " +
21 | "Skipping indicators update.");
22 | return;
23 | }
24 |
25 | // Check if there was never any update or if there wasn't any full
26 | // update in the last 24 hours.
27 | const now = getCurrentUTCDate();
28 | if (full == true || ((cfg.getLastFullUpdateTime() === null) ||
29 | ((now - cfg.getLastFullUpdateTime()) >= ONE_DAY_TIME))) {
30 | console.log("Performing a full update...");
31 | var updateURL = cfg.getAPIIndicatorsURL();
32 | full = true;
33 | } else {
34 | console.log("Only retrieving the latest indicators...");
35 | var updateURL = cfg.getAPIRecentIndicatorsURL();
36 | }
37 |
38 | console.log("Fetching indicators from:", updateURL);
39 |
40 | fetch(updateURL)
41 | .then(function(response) {
42 | // If user is not authorized, then we change the status to unauthorized,
43 | // display warning icon, etc.
44 | if (response.status == 200) {
45 | setStatusAuthorized();
46 | } else if (response.status == 401) {
47 | console.log("ERROR: The indicators update failed: user is not authorized!");
48 | setStatusUnauthorized();
49 | return null;
50 | }
51 |
52 | return response.json();
53 | })
54 | .then(function(data) {
55 | if (data === null)
56 | return;
57 |
58 | // If some other error occurred, return.
59 | if ("error" in data) {
60 | console.log("ERROR: The indicators update failed:", data.error);
61 | return;
62 | }
63 |
64 | // If it's a full update, we store the whole indicators feed.
65 | // Otherwise we only append the updates.
66 | if (full == true) {
67 | console.log("Replacing local indicators with remote list...");
68 | cfg.setIndicators(data, () => {
69 | console.log("Stored full indicator list");
70 | cfg.setLastFullUpdateTime();
71 | });
72 | } else {
73 | console.log("Updating local indicators with only new ones...");
74 |
75 | // First we get the current list.
76 | var indicators = cfg.getIndicators();
77 |
78 | // Then we look for any updates in domains.
79 | for (let i=0; i {
93 | console.log("Stored updated indicator list");
94 | });
95 | }
96 |
97 | console.log("Indicators updated successfully.");
98 | })
99 | .catch(error => {
100 | console.log("ERROR: Fetching indicators failed:", error);
101 | });
102 | }
103 |
--------------------------------------------------------------------------------
/src/js/init.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | function initSuccess() {
19 | console.log("PhishDetect init success");
20 |
21 | // First, we check if the server requires authorization, and if user
22 | // has yet provided an API key.
23 | if (checkKeySetIfNeeded()) {
24 | // If everything is fine, we launch an update of indicators.
25 | updateIndicators();
26 | }
27 |
28 | loadContextMenus();
29 | }
30 |
31 | function initFailure() {
32 | console.log("PhishDetect init failed!");
33 | setStatusOffline();
34 | }
35 |
36 | (function() {
37 | console.log("*** PhishDetect init ***");
38 |
39 | cfg.initStorage(() => {
40 | cfg.fetchNodeConfig(initSuccess, initFailure);
41 | });
42 | })();
43 |
--------------------------------------------------------------------------------
/src/js/reports.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | function sendReport(reportType, reportContent, identifier) {
19 | // Check if the report type is email_*.
20 | if (reportType == "email") {
21 | // If an email identifier was provided...
22 | if (identifier !== undefined && identifier !== null && identifier != "") {
23 | // Get a list of already shared emails.
24 | let emails = cfg.getReportedEmails();
25 | for (let i=0; i response.json())
49 | .then(function(data) {
50 | // We do this to avoid re-sharing already shared emails.
51 | if (reportType == "email") {
52 | if (identifier !== undefined && identifier != "") {
53 | cfg.addReportedEmail(identifier);
54 | }
55 | }
56 |
57 | console.log("Submitted report of type", reportType, "to PhishDetect Node.");
58 |
59 | let confirmationPage = chrome.extension.getURL(REPORT_PAGE) + "?type=" + reportType;
60 | if (reportType == "url")
61 | confirmationPage += "&content="+ reportContent;
62 |
63 | chrome.tabs.create({url: confirmationPage});
64 | })
65 | .catch(error => {
66 | console.log("Failed to submit report: ", error);
67 | chrome.tabs.create({url: chrome.extension.getURL(REPORT_FAILED_PAGE)});
68 | });
69 | }
70 |
--------------------------------------------------------------------------------
/src/js/reviews.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | function sendReview(ioc, tabId) {
19 | const properties = {
20 | method: "POST",
21 | body: JSON.stringify({
22 | "indicator": ioc,
23 | "key": cfg.getApiKey(),
24 | }),
25 | headers: {"Content-Type": "application/json"},
26 | };
27 |
28 | fetch(cfg.getAPIReviewsAddURL(), properties)
29 | .then((response) => response.json())
30 | .then(function(data) {
31 | console.log("Submitted review for indicator", ioc, "to PhishDetect Node.");
32 | chrome.tabs.update(tabId, {url: chrome.extension.getURL(REVIEW_PAGE)});
33 | })
34 | .catch(error => {
35 | console.log("Failed to submit review: ", error);
36 | chrome.tabs.update(tabId, {url: chrome.extension.getURL(REVIEW_FAILED_PAGE)});
37 | });
38 | }
39 |
--------------------------------------------------------------------------------
/src/js/roundcube.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | import scanEmail from "./scanEmail.js";
19 |
20 | // Get the email ID from the URL.
21 | function rcGetOpenEmailUID() {
22 | const url = new URL(location.href);
23 | return url.searchParams.get("_uid");
24 | }
25 |
26 | // Get mbox ID from the URL.
27 | function rcGetOpenEmailMailbox() {
28 | const url = new URL(location.href);
29 | return url.searchParams.get("_mbox");
30 | }
31 |
32 | // Get the email source by invoking Roundcube's "viewsource" view.
33 | function rcGetEmailSource() {
34 | const uid = rcGetOpenEmailUID();
35 | const mbox = rcGetOpenEmailMailbox();
36 |
37 | const url = (window.location.origin +
38 | window.location.pathname +
39 | "?_task=mail&_uid=" + uid +
40 | "&_mbox=" + mbox +
41 | "&_action=viewsource&_extwin=1");
42 |
43 | return new Promise(function(resolve, reject) {
44 | fetch(url)
45 | .then(function(response) {
46 | resolve(response.text());
47 | })
48 | .catch(error => {
49 | reject(error);
50 | });
51 | });
52 | }
53 |
54 | // Try to detect and retrieve the DOM object containing the email.
55 | function rcGetEmail(version) {
56 | if (version == "larry") {
57 | console.log("[PhishDetect] Looking for email in Rouncube Larry template...");
58 |
59 | const iframe = $(".iframe.fullheight");
60 | if (iframe.length) {
61 | console.log("[PhishDetect] Found the iframe. This is likely the two panes view.");
62 | return iframe;
63 | }
64 |
65 | const email = $("#mainscreencontent #mailview-right");
66 | if (email.length) {
67 | console.log("[PhishDetect] Found the full mailview. This is likely the single pane view.");
68 | return email;
69 | }
70 | } else if (version == "elastic") {
71 | console.log("[PhishDetect] Looking for email in Rouncube Elastic template...");
72 |
73 | const iframe = $(".task-mail");
74 | if (iframe.length) {
75 | console.log("[PhishDetect] Found the iframe. This is likely the two panes view.");
76 | return iframe;
77 | }
78 |
79 | const email = $(".content.frame-content");
80 | if (email.length) {
81 | console.log("[PhishDetect] Found the full mailview. This is likely the single pane view.");
82 | return email;
83 | }
84 | }
85 |
86 | console.log("[PhishDetect] No email view found.");
87 |
88 | return null;
89 | }
90 |
91 | // Extract email sender and body and launch a scan.
92 | function rcCheckEmail(email) {
93 | console.log("[PhishDetect] Scanning email ...");
94 |
95 | // Get email body.
96 | const emailBody = email.find("#messagebody");
97 |
98 | // NOTE: If we need to check if the email was already detected, do not
99 | // rely on DOM elements, as those can be faked and the analysis
100 | // could be prevented.
101 |
102 | // We get the email UID.
103 | const uid = rcGetOpenEmailUID();
104 | // If the ID is null, we stop.
105 | if (uid === null) {
106 | return;
107 | }
108 |
109 | console.log("[PhishDetect] Checking email with UID", uid);
110 |
111 | // Get the email sender.
112 | var from = email.find(".rcmContactAddress");
113 |
114 | // If we do not find any sender, we might not have any email open.
115 | if (from === null || from === undefined) {
116 | return;
117 | }
118 |
119 | // Get email sender.
120 | const fromEmail = from.attr("href").toLowerCase().replace("mailto:", "");
121 |
122 | return scanEmail(fromEmail, emailBody, uid);
123 | }
124 |
125 | // Modify the email to add PhishDetect's prompts.
126 | function rcModifyEmail(email) {
127 | console.log("[PhishDetect] Modifying email ...");
128 |
129 | const emailBody = email.find("#messagebody");
130 | if (!emailBody.length) {
131 | return;
132 | }
133 |
134 | var anchors = emailBody.find("a, area");
135 |
136 | chrome.runtime.sendMessage({method: "getNodeEnableAnalysis"}, function(response) {
137 | for (let i=0; i").addClass("roundcube").get(0);
176 | generateReportEmailButton(element, {
177 | uid: uid,
178 | reported: isReported,
179 | getEmailPromise: rcGetEmailSource
180 | });
181 | emailHeader.append(element);
182 | });
183 | }
184 |
185 | window.roundcube = function roundcube(version) {
186 | // NOTE: Currently this is executed inside the main frame as well
187 | // as the message iframe for the two panes view.
188 | var email = rcGetEmail(version);
189 | if (email === null) {
190 | return;
191 | }
192 |
193 | rcReportEmail(email, version);
194 | rcCheckEmail(email);
195 | rcModifyEmail(email);
196 | };
197 |
--------------------------------------------------------------------------------
/src/js/scanEmail.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | // scanEmail will try to determine if any element in the email matches a
19 | // known indicator. In order to do so it will try to:
20 | // 1. Check the full email sender among the list of blocklisted email addresses.
21 | // 2. Check the domain of the email sender among the list of blocklisted domains.
22 | // 3. Check all the anchors in the email among the list of blocklisted domains.
23 | // If it matches anything, it will display a warning, highlight any bad link,
24 | // and send an alert through the "sendAlert" message to the background script.
25 | export default function scanEmail(fromEmail, emailBody, uid) {
26 | console.log("[PhishDetect] Checking email sender:", fromEmail);
27 |
28 | const fromEmailHash = sha256(fromEmail);
29 | let fromEmailDomain = "";
30 | let fromEmailDomainHash = "";
31 | let fromEmailTopDomain = "";
32 | let fromEmailTopDomainHash = "";
33 |
34 | // We extract the domain from the email address.
35 | let parts = fromEmail.split("@");
36 | if (parts.length === 2) {
37 | fromEmailDomain = getDomainFromURL(parts[1]);
38 | fromEmailDomainHash = sha256(fromEmailDomain);
39 | fromEmailTopDomain = getTopDomainFromURL(parts[1]);
40 | fromEmailTopDomainHash = sha256(fromEmailTopDomain);
41 | }
42 |
43 | // First we get the list of indicators.
44 | chrome.runtime.sendMessage({method: "getIndicators"}, function(response) {
45 | // Fail if we don't have any indicators.
46 | if (response == "") {
47 | return false;
48 | }
49 | const indicators = response;
50 |
51 | if (indicators === undefined) {
52 | return false;
53 | }
54 |
55 | // Email status.
56 | let isEmailBad = false;
57 | let alertType = "";
58 | let alertMatch = "";
59 | let alertIndicator = "";
60 |
61 | // We check for email addresses, if we have any indicators to check.
62 | if (indicators.emails !== null) {
63 | let itemsToCheck = [fromEmailHash,];
64 | let matchedIndicator = checkForIndicators(itemsToCheck, indicators.emails);
65 | if (matchedIndicator !== null) {
66 | console.log("[PhishDetect] Detected bad email sender with indicator:", matchedIndicator);
67 |
68 | // Mark email as bad.
69 | isEmailBad = true;
70 | alertType = "email_sender";
71 | alertMatch = fromEmail;
72 | alertIndicator = matchedIndicator;
73 | }
74 | }
75 |
76 | // TODO: Need to review the performance of this.
77 | // We check for domains, if we have any indicators to check.
78 | if (indicators.domains !== null) {
79 | // First we check the domain of the email sender.
80 | let itemsToCheck = [fromEmailDomainHash, fromEmailTopDomainHash];
81 | let matchedIndicator = checkForIndicators(itemsToCheck, indicators.domains);
82 | if (matchedIndicator !== null) {
83 | console.log("[PhishDetect] Detected email sender domain with indicator:", matchedIndicator);
84 |
85 | // Mark whole email as bad.
86 | // TODO: this is ugly.
87 | isEmailBad = true;
88 | alertType = "email_sender_domain";
89 | alertMatch = fromEmail;
90 | alertIndicator = matchedIndicator;
91 | }
92 |
93 | // Now we check for links contained in the emails body.
94 | // We extract all links from the body of the email.
95 | let anchors = $(emailBody).find("a, area");
96 |
97 | // TODO: Might want to reverse these loops for performance reasons.
98 | for (let i=0; i.
17 |
18 | function scanBrowsingHistory(tabId) {
19 | console.log("Scanning browsing history...");
20 |
21 | const indicators = cfg.getIndicators();
22 | if (indicators.domains === undefined || indicators.domains.length == 0) {
23 | console.log("No indicators to use for scanning browsing history. Skip.");
24 | } else {
25 | chrome.history.search({text: "", startTime: 0}, function(items) {
26 | console.log("Found a total of", items.length, "history items.");
27 |
28 | for (let i=0; i.
17 |
18 | // Analyze a link (coming from webmail and context menu).
19 | function scanLink(link) {
20 | console.log("Received request to analyze link", link);
21 | chrome.tabs.create({url: chrome.extension.getURL(SCAN_PAGE)}, function(tab) {
22 | chrome.tabs.onUpdated.addListener(function onUpdated(updatedTabId, updatedTabInfo) {
23 | if (tab.id == updatedTabId && updatedTabInfo.status === "complete") {
24 | chrome.tabs.sendMessage(tab.id, {method: "scanLink", url: link});
25 | }
26 | });
27 | });
28 | }
29 |
--------------------------------------------------------------------------------
/src/js/scanPage.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | // Analyze an HTML page (taken from an open tab).
19 | // Welcome to callback hell.
20 | function scanPage(tabId, tabUrl) {
21 | console.log("Received request to analyze page at tab", tabId, "with URL", tabUrl);
22 | chrome.tabs.captureVisibleTab(null, {}, function(img) {
23 | chrome.tabs.executeScript(tabId, {file: "./js/getTabHTML.js"}, function() {
24 | if (chrome.runtime.lastError) {
25 | // TODO: Show error page.
26 | console.error("Failed to execute getTabHTML: " + chrome.runtime.lastError.message);
27 | return;
28 | }
29 |
30 | chrome.tabs.sendMessage(tabId, {method: "getTabHTML"}, function(response) {
31 | var encodedHTML = base64encode(response);
32 |
33 | chrome.tabs.update(tabId, {url: chrome.extension.getURL(SCAN_PAGE)}, function() {
34 | chrome.tabs.onUpdated.addListener(function onUpdated(updatedTabId, updatedTabInfo) {
35 | if (tabId == updatedTabId && updatedTabInfo.status === "complete") {
36 | chrome.tabs.sendMessage(tabId, {method: "scanHTML", url: tabUrl, screenshot: img, html: encodedHTML});
37 | }
38 | });
39 | });
40 | });
41 | });
42 | });
43 | }
44 |
--------------------------------------------------------------------------------
/src/js/status.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | function setStatusOnline() {
19 | console.log("PhishDetect Node is online! Yay?");
20 | cfg.status = "online";
21 | }
22 |
23 | function setStatusOffline() {
24 | console.log("PhishDetect Node is offline! Setting status to offline too.");
25 | chrome.browserAction.setIcon({path: chrome.extension.getURL("icons/128x128/icon_gray.png")});
26 | cfg.status = "offline";
27 | }
28 |
29 | function setStatusAuthorized() {
30 | console.log("Setting status as authorized");
31 | chrome.browserAction.setIcon({path: chrome.extension.getURL("icons/128x128/icon.png")});
32 | cfg.status = "authorized";
33 | }
34 |
35 | function setStatusUnauthorized() {
36 | console.log("Setting status as unauthorized!");
37 | chrome.browserAction.setIcon({path: chrome.extension.getURL("icons/128x128/icon_error.png")});
38 | cfg.status = "unauthorized";
39 | }
40 |
41 | function checkKeySetIfNeeded() {
42 | // If the node enforces authentication, but we don't have an API key,
43 | // we show an error icon in the toolbar.
44 | if (cfg.getNodeEnforceUserAuth() === true && cfg.getApiKey() == "") {
45 | console.log("The user does not appear to have configured a required API key!");
46 | chrome.browserAction.setIcon({path: chrome.extension.getURL("icons/128x128/icon_error.png")});
47 | cfg.status = "authorization_needed";
48 | return false;
49 | }
50 |
51 | return true;
52 | }
53 |
--------------------------------------------------------------------------------
/src/js/utils.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | // This is a helper function to check hashes against the list of
19 | // malicious indicators.
20 | function checkForIndicators(items, indicators) {
21 | for (let i=0; i").text(translation);
53 | return textNode.html(); // return html content
54 | }
55 |
56 | // This function is used by UI pages to obtain their tab ID.
57 | function getTab(callback) {
58 | let url = new URL(window.location.href);
59 | if (url.searchParams.has("tabId")) {
60 | let parentId = Number(url.searchParams.get("tabId"));
61 | return chrome.tabs.get(parentId, callback);
62 | }
63 | chrome.tabs.query({active: true, lastFocusedWindow: true}, tabs => callback(tabs[0]));
64 | }
65 |
--------------------------------------------------------------------------------
/src/js/validate.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | "use strict";
19 |
20 | const validator = require("validator");
21 |
22 | window.isEmpty = function isEmpty(s) {
23 | return validator.isEmpty(s);
24 | }
25 |
26 | window.isEmail = function isEmail(s) {
27 | return validator.isEmail(s);
28 | }
29 |
30 | window.isIP = function isIP(s) {
31 | return validator.isIP(s);
32 | }
33 |
34 | window.normalizeName = function normalizeName(name) {
35 | return validator.escape(name);
36 | }
37 |
38 | window.normalizeEmail = function normalizeEmail(email) {
39 | return validator.normalizeEmail(email);
40 | }
41 |
--------------------------------------------------------------------------------
/src/js/webmails.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | function checkRoundcube() {
19 | console.log("[PhishDetect] Checking for Roundcube ...");
20 |
21 | var foundLarryCSS = false;
22 | var foundElasticCSS = false;
23 |
24 | const links = $("link");
25 | if (links.length == 0) {
26 | return false;
27 | }
28 |
29 | for (let i=0; i= 0) {
32 | foundLarryCSS = true;
33 | break;
34 | } else if (href.indexOf("/elastic/") >= 0) {
35 | foundElasticCSS = true;
36 | break;
37 | }
38 | }
39 |
40 | if ($("#messageheader, #messagebody").length && foundLarryCSS) {
41 | console.log("[PhishDetect] Found Roundcube with Larry template!");
42 | roundcube("larry");
43 | return true;
44 | }
45 |
46 | if ($("#layout-content, #message-header, #message-content").length && foundElasticCSS) {
47 | console.log("[PhishDetect] Found Roundcube with Elastic template!");
48 | roundcube("elastic");
49 | return true;
50 | }
51 |
52 | return false
53 | }
54 |
55 | $(document).ready(function() {
56 | // Check if the option to integrate with webmails is enabled.
57 | chrome.runtime.sendMessage({method: "getWebmailsIntegration"}, function(response) {
58 | // If not, we stop straight away.
59 | if (response === false) {
60 | return;
61 | }
62 |
63 | console.log("[PhishDetect] Checking for any supported webmail...");
64 |
65 | if (checkRoundcube()) {
66 | return;
67 | }
68 | });
69 | });
70 |
--------------------------------------------------------------------------------
/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 |
4 | "name": "PhishDetect",
5 | "description": "__MSG_manifestDescription__",
6 | "default_locale": "en",
7 | "icons": {
8 | "16": "icons/16x16/icon.png",
9 | "32": "icons/32x32/icon.png",
10 | "48": "icons/48x48/icon.png",
11 | "128": "icons/128x128/icon.png"
12 | },
13 | "browser_action": {
14 | "default_icon": "icons/128x128/icon.png",
15 | "default_popup": "ui/popup/popup.html"
16 | },
17 | "browser_specific_settings": {
18 | "gecko": {
19 | "id": "{32e37b7c-894f-47bf-a68b-75f939276910}"
20 | }
21 | },
22 | "options_ui" : {
23 | "page": "ui/settings/settings.html"
24 | },
25 | "background": {
26 | "scripts": [
27 | "lib/sha256.min.js",
28 | "dist/domains.js",
29 | "dist/validate.js",
30 | "js/utils.js",
31 | "js/const.js",
32 | "js/indicators.js",
33 | "js/alerts.js",
34 | "js/reports.js",
35 | "js/reviews.js",
36 | "js/alarms.js",
37 | "js/scanLink.js",
38 | "js/scanPage.js",
39 | "js/scanHistory.js",
40 | "js/config.js",
41 | "js/background.js",
42 | "js/status.js",
43 | "js/init.js"
44 | ]
45 | },
46 | "permissions": [
47 | "alarms",
48 | "activeTab",
49 | "tabs",
50 | "contextMenus",
51 | "webRequest",
52 | "webRequestBlocking",
53 | "history",
54 | "storage",
55 | "unlimitedStorage",
56 | ""
57 | ],
58 | "content_scripts": [
59 | {
60 | "matches": ["https://mail.google.com/*"],
61 | "js": [
62 | "lib/jquery.min.js",
63 | "lib/sha256.min.js",
64 | "dist/domains.js",
65 | "dist/gui.js",
66 | "js/utils.js",
67 | "dist/gmail.js"
68 | ],
69 | "css": [
70 | "css/vex.css",
71 | "css/vex-theme-default.css",
72 | "css/phishdetect-webmails.css"
73 | ],
74 | "run_at": "document_end"
75 | },
76 | {
77 | "matches": ["http://*/*", "https://*/*"],
78 | "exclude_matches": ["https://mail.google.com/*"],
79 | "js": [
80 | "lib/jquery.min.js",
81 | "lib/sha256.min.js",
82 | "dist/domains.js",
83 | "dist/gui.js",
84 | "js/utils.js",
85 | "dist/roundcube.js",
86 | "js/webmails.js"
87 | ],
88 | "css": [
89 | "css/vex.css",
90 | "css/vex-theme-default.css",
91 | "css/phishdetect-webmails.css"
92 | ],
93 | "run_at": "document_end",
94 | "all_frames": true
95 | }
96 | ],
97 | "web_accessible_resources": [
98 | "fontawesome/",
99 | "ui/warning/warning.html"
100 | ]
101 | }
102 |
--------------------------------------------------------------------------------
/src/ui/history/history.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PhishDetect - Scan Browsing History
6 |
7 |
8 |
9 |
10 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/ui/history/history.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | import React from "react";
19 | import ReactDOM from "react-dom";
20 | import HistoryAlerts from "../../components/HistoryAlerts";
21 |
22 | chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
23 | switch (request.method) {
24 | case "historyMatchFound":
25 | const url = request.match.url;
26 | const dateTime = new Date(request.match.visitTime).toString();
27 | window.alertsRendered.onAddAlert(dateTime, url);
28 | break;
29 | case "historyScanCompleted":
30 | $("#statusInProgress").hide();
31 | $("#statusCompleted").show();
32 |
33 | // TODO: This doesn't currently work because of a race condition in
34 | // receiving the alert messages. The scan is often completed
35 | // before all messages were received.
36 | // if ($("#alerts > *").length == 0) {
37 | // $("#alerts").hide();
38 | // $("#nothingFound").show();
39 | // }
40 | break;
41 | }
42 | });
43 |
44 | document.addEventListener("DOMContentLoaded", function() {
45 | $("#startButton").click(function() {
46 | ReactDOM.render( {window.alertsRendered = alertsRendered;}} />, $("#alerts").get(0));
47 | $("#startButton").hide();
48 | $("#statusInProgress").show();
49 | getTab(function(tab) {
50 | chrome.runtime.sendMessage({method: "scanHistory", tabId: tab.id});
51 | });
52 | });
53 | });
54 |
--------------------------------------------------------------------------------
/src/ui/onboarding/join.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PhishDetect - Network
6 |
7 |
8 |
9 |
10 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/ui/onboarding/join.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | $("#next").on("click", function() {
19 | const address = $("#networkAddress").val();
20 | if (address == "") {
21 | $("#errorMessage").text("You need to provide a valid PhishDetect Network");
22 | return;
23 | }
24 |
25 | console.log("Updating node to " + address);
26 | chrome.runtime.sendMessage({method: "updateNode", node: address}, function(response) {
27 | if (!cfg.getNodeEnforceUserAuth()) {
28 | getTab(function(tab) {
29 | chrome.tabs.update(tab.id, {url: chrome.extension.getURL("ui/onboarding/joined.html")});
30 | });
31 | }
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/src/ui/onboarding/joined.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PhishDetect - Network
6 |
7 |
8 |
9 |
10 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/ui/onboarding/joined.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | document.addEventListener("DOMContentLoaded", cfg.loadFromBackground(function() {
19 | $("#networkAddress").text(cfg.getNode());
20 | $("#adminContacts").text(cfg.getNodeAdminContacts());
21 | }));
22 |
--------------------------------------------------------------------------------
/src/ui/onboarding/register.js:
--------------------------------------------------------------------------------
1 | // // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | // //
3 | // // This file is part of PhishDetect.
4 | // //
5 | // // PhishDetect is free software: you can redistribute it and/or modify
6 | // // it under the terms of the GNU General Public License as published by
7 | // // the Free Software Foundation, either version 3 of the License, or
8 | // // (at your option) any later version.
9 | // //
10 | // // PhishDetect is distributed in the hope that it will be useful,
11 | // // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // // GNU General Public License for more details.
14 | // //
15 | // // You should have received a copy of the GNU General Public License
16 | // // along with PhishDetect. If not, see .
17 |
18 | // function register(event) {
19 | // $("#nameError").text("");
20 | // $("#emailError").text("");
21 |
22 | // let name = $("#name").val().trim();
23 | // let email = $("#email").val().trim();
24 |
25 | // // If it's an empty name, we return.
26 | // if (isEmpty(name)) {
27 | // $("#nameError").text(chrome.i18n.getMessage("registerErrorInvalidName"));
28 | // return;
29 | // }
30 |
31 | // // If it's an invalid email, we return.
32 | // if (!isEmail(email)) {
33 | // $("#emailError").text(chrome.i18n.getMessage("registerErrorInvalidEmail"));
34 | // return;
35 | // }
36 |
37 | // name = normalizeName(name);
38 | // email = normalizeEmail(email);
39 |
40 | // const properties = {
41 | // method: "POST",
42 | // body: JSON.stringify({
43 | // "name": name,
44 | // "email": email,
45 | // }),
46 | // headers: {"Content-Type": "application/json"},
47 | // };
48 |
49 | // fetch(cfg.getAPIRegisterURL(), properties)
50 | // .then((response) => response.json())
51 | // .then(function(data) {
52 | // if ("error" in data) {
53 | // console.log("Failed to register new user: ", data.error);
54 | // $("#emailError").text(data.error);
55 | // return;
56 | // }
57 |
58 | // // Save the reitrieved API key.
59 | // cfg.setApiKey(data.key);
60 |
61 | // // Instruct the background to update the configuration.
62 | // chrome.runtime.sendMessage({method: "updateConfiguration", config: cfg.config}, function(response) {
63 | // // We request an update of indicators, which is also used to
64 | // // update the status in the popup.
65 | // chrome.runtime.sendMessage({method: "updateIndicators"});
66 |
67 | // // Then we redirect to the completion page.
68 | // getTab(function(tab) {
69 | // chrome.tabs.update(tab.id, {url: chrome.extension.getURL(REGISTER_COMPLETED_PAGE)});
70 | // });
71 | // });
72 | // })
73 | // .catch(error => {
74 | // console.log("Failed to register new user: ", error);
75 | // $("#registrationFailed").text(error).show();
76 | // });
77 | // }
78 |
79 | // document.addEventListener("DOMContentLoaded", cfg.loadFromBackground());
80 | // $("#buttonRegister").click(register);
81 |
--------------------------------------------------------------------------------
/src/ui/onboarding/three_fish.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phishdetect/phishdetect-extension/fd481f76dfba7d8e2ca97726927fae6d18db668e/src/ui/onboarding/three_fish.png
--------------------------------------------------------------------------------
/src/ui/popup/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/ui/settings/leave.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | document.addEventListener("DOMContentLoaded", cfg.loadFromBackground(function() {
19 | $("#networkAddress").text(cfg.getNode());
20 | }));
21 |
--------------------------------------------------------------------------------
/src/ui/settings/leave_confirm.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PhishDetect - Settings
6 |
7 |
8 |
9 |
10 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/ui/settings/leave_confirm.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2021 Claudio Guarnieri.
2 | //
3 | // This file is part of PhishDetect.
4 | //
5 | // PhishDetect is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // PhishDetect is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with PhishDetect. If not, see .
17 |
18 | document.addEventListener("DOMContentLoaded", cfg.loadFromBackground(function() {
19 | $("#networkAddress").text(cfg.getNode());
20 | chrome.runtime.sendMessage({method: "updateNode", node: NODE_DEFAULT_URL});
21 | }));
22 |
--------------------------------------------------------------------------------
/src/ui/settings/settings.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PhishDetect - Settings
6 |
7 |
8 |
9 |
10 |
PhishDetect will locally scan your browser history and check the websites you visited against confirmed phishing websites. You can perform this scan right after installing PhishDetect, spontaneously, or for research purposes.
A member of your PhishDetect Network reported this link to the Network's admin. The admin confirmed that it is a phishing link, the extension blocked it for everyone in your PhishDetect Network.
31 |
32 |
Has anything bad happened?
33 |
If you have not entered a login, password, or sensitive information, it's unlikely anything bad happened.
34 |
35 |
What are potential risks of phishing?
36 |
37 |
38 |
Bad hackers could take over your online accounts
39 |
Bad hackers could expose your personal information