|
1 | 1 | /* global chrome, console, exports, CryptoJS, Emitter */ |
2 | 2 |
|
3 | | -var repoUrl = "https://raw.githubusercontent.com/RetireJS/retire.js/master/repository/jsrepository.json"; |
4 | | -var updatedAt = Date.now(); |
5 | | -var repo; |
6 | | -var repoFuncs; |
7 | | - |
8 | | -var vulnerable = {}; |
9 | | -var events = new Emitter(); |
10 | | -var sandboxWin; |
11 | | -var scanEnabled = true; |
12 | | - |
13 | | -var hasher = { |
14 | | - sha1 : function(data) { |
15 | | - return CryptoJS.SHA1(data).toString(CryptoJS.enc.Hex); |
16 | | - } |
| 3 | +const repoUrl = |
| 4 | + "https://raw.githubusercontent.com/RetireJS/retire.js/master/repository/jsrepository.json"; |
| 5 | +let updatedAt = Date.now(); |
| 6 | +let repo; |
| 7 | +let repoFuncs; |
| 8 | + |
| 9 | +let vulnerable = {}; |
| 10 | +const events = new Emitter(); |
| 11 | +let sandboxWin; |
| 12 | + |
| 13 | +const hasher = { |
| 14 | + sha1: function (data) { |
| 15 | + return CryptoJS.SHA1(data).toString(CryptoJS.enc.Hex); |
| 16 | + }, |
17 | 17 | }; |
18 | 18 |
|
19 | | -function download(url) { |
20 | | - var events = new Emitter(); |
21 | | - var xhr = new XMLHttpRequest(); |
22 | | - xhr.onreadystatechange = function() { |
23 | | - if (xhr.readyState == 4) { |
24 | | - if (xhr.status == 200) { |
25 | | - events.emit('success', xhr.responseText); |
26 | | - } else { |
27 | | - console.log("Got " + xhr.status + " when trying to download " + url); |
28 | | - } |
29 | | - } |
30 | | - return true; |
31 | | - }; |
32 | | - xhr.open("GET", url, true); |
33 | | - xhr.send(); |
34 | | - return events; |
| 19 | +async function download(url) { |
| 20 | + const response = await fetch(url); |
| 21 | + if (response.ok) { |
| 22 | + return response.text(); |
| 23 | + } else { |
| 24 | + throw new Error( |
| 25 | + "Got " + response.status + " when trying to download " + url |
| 26 | + ); |
| 27 | + } |
35 | 28 | } |
36 | 29 |
|
37 | | -function downloadRepo() { |
38 | | - var events = new Emitter(); |
39 | | - console.log("Downloading repo ..."); |
40 | | - updatedAt = Date.now(); |
41 | | - download(repoUrl + "?" + updatedAt).on('success', function(repoData) { |
42 | | - repo = JSON.parse(retire.replaceVersion(repoData)); |
43 | | - console.log("Done"); |
44 | | - vulnerable = {}; |
45 | | - setFuncs(); |
46 | | - events.emit('success'); |
47 | | - return true; |
48 | | - }); |
49 | | - return events; |
| 30 | +async function downloadRepo() { |
| 31 | + console.log("Downloading repo ..."); |
| 32 | + updatedAt = Date.now(); |
| 33 | + const repoData = await download(repoUrl + "?" + updatedAt); |
| 34 | + repo = JSON.parse(retire.replaceVersion(repoData)); |
| 35 | + console.log("Done"); |
| 36 | + vulnerable = {}; |
| 37 | + setFuncs(); |
50 | 38 | } |
51 | 39 |
|
52 | 40 | function setFuncs() { |
53 | | - repoFuncs = {}; |
54 | | - for (var component in repo) { |
55 | | - if (repo[component].extractors.func) { |
56 | | - repoFuncs[component] = repo[component].extractors.func; |
57 | | - } |
58 | | - } |
| 41 | + repoFuncs = {}; |
| 42 | + for (var component in repo) { |
| 43 | + if (repo[component].extractors.func) { |
| 44 | + repoFuncs[component] = repo[component].extractors.func; |
| 45 | + } |
| 46 | + } |
59 | 47 | } |
60 | 48 |
|
61 | 49 | function getFileName(url) { |
62 | | - var a = document.createElement("a"); |
63 | | - a.href = url; |
64 | | - return (a.pathname.match(/\/([^\/?#]+)$/i) || [,""])[1]; |
| 50 | + var a = document.createElement("a"); |
| 51 | + a.href = url; |
| 52 | + return (a.pathname.match(/\/([^\/?#]+)$/i) || [, ""])[1]; |
65 | 53 | } |
66 | 54 |
|
67 | | - |
68 | | - |
69 | | -events.on('scan', function(details) { |
70 | | - if (details.url.indexOf('chrome-extension://') === 0) return true; |
71 | | - |
72 | | - if ((Date.now() - updatedAt) > 1000*60*60*6) { |
73 | | - downloadRepo().on('success', function() { events.emit('scan', details); }); |
74 | | - return true; |
75 | | - } |
76 | | - events.emit('result-ready', details, []); |
77 | | - console.log("Scanning " + details.url + " ..."); |
78 | | - var results = retire.scanUri(details.url, repo); |
79 | | - if (results.length > 0) { |
80 | | - events.emit('result-ready', details, results); |
81 | | - return true; |
82 | | - } |
83 | | - results = retire.scanFileName(getFileName(details.url), repo); |
84 | | - if (results.length > 0) { |
85 | | - events.emit('result-ready', details, results); |
86 | | - return true; |
87 | | - } |
88 | | - download(details.url).on('success', function(content) { |
89 | | - events.emit('script-downloaded', details, content); |
90 | | - return true; |
91 | | - }); |
92 | | - return true; |
| 55 | +events.on("scan", function (details) { |
| 56 | + if (details.url.indexOf("chrome-extension://") === 0) return true; |
| 57 | + |
| 58 | + if (Date.now() - updatedAt > 1000 * 60 * 60 * 6) { |
| 59 | + downloadRepo().then(() => { |
| 60 | + events.emit("scan", details); |
| 61 | + }); |
| 62 | + return; |
| 63 | + } |
| 64 | + events.emit("result-ready", details, []); |
| 65 | + console.log("Scanning " + details.url + " ..."); |
| 66 | + var results = retire.scanUri(details.url, repo); |
| 67 | + if (results.length > 0) { |
| 68 | + events.emit("result-ready", details, results); |
| 69 | + return; |
| 70 | + } |
| 71 | + results = retire.scanFileName(getFileName(details.url), repo); |
| 72 | + if (results.length > 0) { |
| 73 | + events.emit("result-ready", details, results); |
| 74 | + return; |
| 75 | + } |
| 76 | + download(details.url).then((content) => { |
| 77 | + events.emit("script-downloaded", details, content); |
| 78 | + }); |
93 | 79 | }); |
94 | 80 |
|
95 | | -events.on('script-downloaded', function(details, content) { |
96 | | - var results = retire.scanFileContent(content, repo, hasher); |
97 | | - if (results.length > 0) { |
98 | | - events.emit('result-ready', details, results); |
99 | | - return true; |
100 | | - } |
101 | | - events.emit('sandbox', details, content); |
102 | | - console.log(hasher.sha1(content) + " : " + details.url); |
103 | | - return true; |
| 81 | +events.on("script-downloaded", function (details, content) { |
| 82 | + var results = retire.scanFileContent(content, repo, hasher); |
| 83 | + if (results.length > 0) { |
| 84 | + events.emit("result-ready", details, results); |
| 85 | + return true; |
| 86 | + } |
| 87 | + events.emit("sandbox", details, content); |
| 88 | + console.log(hasher.sha1(content) + " : " + details.url); |
| 89 | + return true; |
104 | 90 | }); |
105 | 91 |
|
106 | | -events.on('sandbox', function(details, content) { |
107 | | - sandboxWin.postMessage({ tabId: details.tabId, script : content, url: details.url, repoFuncs: repoFuncs }, "*"); |
108 | | - return true; |
| 92 | +events.on("sandbox", function (details, content) { |
| 93 | + sandboxWin.postMessage( |
| 94 | + { |
| 95 | + tabId: details.tabId, |
| 96 | + script: content, |
| 97 | + url: details.url, |
| 98 | + repoFuncs: repoFuncs, |
| 99 | + }, |
| 100 | + "*" |
| 101 | + ); |
| 102 | + return true; |
109 | 103 | }); |
110 | 104 |
|
111 | | -window.addEventListener("message", function(evt) { |
112 | | - if (evt.data.version) { |
113 | | - var results = retire.check(evt.data.component, evt.data.version, repo); |
114 | | - console.log("SANDBOX", stringifyResults(results)); |
115 | | - events.emit('result-ready', { url : evt.data.original.url, tabId : evt.data.original.tabId }, results); |
116 | | - } |
117 | | - return true; |
| 105 | +window.addEventListener("message", function (evt) { |
| 106 | + if (evt.data.version) { |
| 107 | + var results = retire.check(evt.data.component, evt.data.version, repo); |
| 108 | + console.log("SANDBOX", stringifyResults(results)); |
| 109 | + events.emit( |
| 110 | + "result-ready", |
| 111 | + { url: evt.data.original.url, tabId: evt.data.original.tabId }, |
| 112 | + results |
| 113 | + ); |
| 114 | + } |
| 115 | + return true; |
118 | 116 | }); |
119 | 117 |
|
120 | 118 | function stringifyResults(results) { |
121 | | - return results.map(x => "\n" + x.component + ":" + x.version).reduce((a,b) => a + b, ""); |
| 119 | + return results |
| 120 | + .map((x) => "\n" + x.component + ":" + x.version) |
| 121 | + .reduce((a, b) => a + b, ""); |
122 | 122 | } |
123 | 123 |
|
124 | | -events.on('result-ready', function(details, results) { |
125 | | - var vulnerable = retire.isVulnerable(results); |
126 | | - if (vulnerable) { |
127 | | - console.log(details.url, stringifyResults(results)); |
128 | | - chrome.browserAction.setBadgeText({text : "!", tabId : details.tabId }); |
129 | | - } |
130 | | - if (!vulnerable) console.log(details.url, stringifyResults(results)); |
131 | | - |
132 | | - vulnerable[details.url] = results; |
133 | | - |
134 | | - var result = { vulnerable: vulnerable, results: results, url: details.url }; |
135 | | - setTimeout(function() { |
136 | | - if (details.tabId >= 0) { |
137 | | - chrome.tabs.sendMessage(details.tabId, { |
138 | | - message : JSON.stringify(result) |
139 | | - }, function(response) { |
140 | | - let e = chrome.runtime.lastError |
141 | | - if (e) console.log("Failed to send message:" + e); |
142 | | - console.log(details.tabId); |
143 | | - if (response) { |
144 | | - chrome.browserAction.setBadgeText({text : "" + response.count, tabId : details.tabId }); |
145 | | - } |
146 | | - return true; |
147 | | - }); |
148 | | - } |
149 | | - return true; |
150 | | - }, 3000); |
151 | | -}); |
| 124 | +events.on("result-ready", function (details, results) { |
| 125 | + var vulnerable = retire.isVulnerable(results); |
| 126 | + if (vulnerable) { |
| 127 | + console.log(details.url, stringifyResults(results)); |
| 128 | + } |
| 129 | + if (!vulnerable) console.log(details.url, stringifyResults(results)); |
152 | 130 |
|
153 | | -chrome.browserAction.setBadgeBackgroundColor({ color: [255, 0, 0, 255] }); |
154 | | - |
155 | | - |
156 | | -chrome.extension.onRequest.addListener(function(request, sender, sendResponse) { |
157 | | - if (request.to !== 'background') { |
158 | | - return true; |
159 | | - } |
160 | | - if (request.message === 'enabled?') { |
161 | | - sendResponse({ enabled : scanEnabled }); |
162 | | - return true; |
163 | | - } |
164 | | - if (request.message === 'enable') { |
165 | | - scanEnabled = request.data; |
166 | | - } |
167 | | - return true; |
168 | | -}); |
| 131 | + vulnerable[details.url] = results; |
169 | 132 |
|
| 133 | + var result = { vulnerable: vulnerable, results: results, url: details.url }; |
| 134 | + chrome.runtime.sendMessage({ type: "result", result, details }); |
| 135 | +}); |
170 | 136 |
|
171 | | -downloadRepo().on('success', function() { |
172 | | - var filter = { |
173 | | - "urls" : ["<all_urls>"], |
174 | | - "types" : ["script"] |
175 | | - }; |
176 | | - function scan(details) { |
177 | | - if (details.method === "GET" && scanEnabled) { |
178 | | - events.emit('scan', details); |
179 | | - } |
180 | | - return true; |
181 | | - } |
182 | | - chrome.webRequest.onCompleted.addListener(scan, filter, []); |
183 | | - return true; |
| 137 | +downloadRepo().then(() => { |
| 138 | + chrome.runtime.sendMessage({ type: "repo-ready" }); |
| 139 | + chrome.runtime.onMessage.addListener((msg) => { |
| 140 | + if (msg.type === "scan") { |
| 141 | + events.emit("scan", msg.details); |
| 142 | + } else { |
| 143 | + console.log("Background", msg); |
| 144 | + } |
| 145 | + }); |
184 | 146 | }); |
185 | 147 |
|
186 | 148 | sandboxWin = window.document.getElementById("sandboxframe").contentWindow; |
187 | | - |
188 | | - |
189 | | - |
190 | | - |
191 | | - |
0 commit comments