├── LICENSE
├── README.md
├── debugHunter.js
├── images
├── banner.png
└── icon.png
├── manifest.json
├── options.html
├── options.js
├── popup.html
├── popup.js
└── similarity.min.js
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 devploit
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # debugHunter - Chrome Extension
2 |
3 |
4 |
5 |
6 |
7 | [](https://github.com/devploit/debugHunter/issues/)
8 |
9 | Discover hidden debugging parameters and uncover web application secrets with debugHunter. This Chrome extension scans websites for debugging parameters and notifies you when it finds a URL with modified responses. The extension utilizes a binary search algorithm to efficiently determine the parameter responsible for the change in the response.
10 |
11 | ## Features
12 |
13 | - Compare responses with and without query parameters to identify changes.
14 | - Compare responses with and without custom headers to identify changes.
15 | - Check for sensitive paths.
16 | - Avoid dynamic URLs and it's false positives.
17 | - Avoid soft 404 URLs.
18 | - Track and display the number of modified URLs in the browser action badge.
19 | - Allow the user to view and clear the list of found URLs.
20 |
21 | ## Installation
22 |
23 | ### Option 1: Clone the repository
24 |
25 | 1. Download or clone this repository to your local machine.
26 | 2. Open Google Chrome, and go to `chrome://extensions/`.
27 | 3. Enable "Developer mode" in the top right corner if it's not already enabled.
28 | 4. Click the "Load unpacked" button on the top left corner.
29 | 5. Navigate to the directory where you downloaded or cloned the repository, and select the folder.
30 | 6. The debugHunter extension should now be installed and ready to use.
31 |
32 | ### Option 2: Download the release (.zip)
33 |
34 | 1. Download the latest release `.zip` file from the "Releases" section of this repository.
35 | 2. Extract the contents of the `.zip` file to a folder on your local machine.
36 | 3. Open Google Chrome, and go to `chrome://extensions/`.
37 | 4. Enable "Developer mode" in the top right corner if it's not already enabled.
38 | 5. Click the "Load unpacked" button on the top left corner.
39 | 6. Navigate to the directory where you extracted the `.zip` file, and select the folder.
40 | 7. The debugHunter extension should now be installed and ready to use.
41 |
42 | ## Usage
43 |
44 | It is recommended to pin the extension to the toolbar to check if a new modified URL by debug parameter is found.
45 | 1. Navigate to any website.
46 | 2. Click on the debugHunter extension icon in the Chrome toolbar.
47 | 3. If the extension detects any URLs with modified responses due to debugging parameters, they will be listed in the popup.
48 | 4. Click on any URL in the list to open it in a new tab.
49 | 5. To clear the list, click on the trash can icon in the top right corner of the popup.
50 |
51 | ## Options/Customization
52 |
53 | To modify the similarity threshold using the options page of the extension, follow these steps:
54 | 1. Click on the debugHunter extension icon in the Chrome toolbar.
55 | 2. Click on the gear icon in the top right corner of the popup to open the options page.
56 | 3. In the options page, use the slider to set the similarity threshold to the desired value (default 0.95).
57 |
58 | ## Contributing
59 |
60 | We welcome contributions! Please feel free to submit pull requests or open issues to improve debugHunter.
61 |
62 | ## License
63 |
64 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
65 |
--------------------------------------------------------------------------------
/debugHunter.js:
--------------------------------------------------------------------------------
1 | /*
2 | QUERY PARAMS
3 | */
4 |
5 | // List of query params to be tested
6 | const queryParams = [
7 | { key: "_debug", value: "1" },
8 | { key: "admin", value: "1" },
9 | { key: "analysis", value: "1" },
10 | { key: "beta", value: "1" },
11 | { key: "console", value: "1" },
12 | { key: "debug", value: "1" },
13 | { key: "debug_flag", value: "1" },
14 | { key: "debug_mode", value: "1" },
15 | { key: "debug_output", value: "1" },
16 | { key: "debug_status", value: "1" },
17 | { key: "debuginfo", value: "1" },
18 | { key: "debuglevel", value: "1" },
19 | { key: "dev", value: "1" },
20 | { key: "dev_mode", value: "1" },
21 | { key: "development", value: "1" },
22 | { key: "diagnostic", value: "1" },
23 | { key: "env", value: "pre" },
24 | { key: "error_reporting", value: "1" },
25 | { key: "experiment", value: "1" },
26 | { key: "internal", value: "1" },
27 | { key: "log", value: "1" },
28 | { key: "mode", value: "debug" },
29 | { key: "monitoring", value: "1" },
30 | { key: "performance", value: "1" },
31 | { key: "profiler", value: "1" },
32 | { key: "qa", value: "1" },
33 | { key: "sandbox", value: "1" },
34 | { key: "show_errors", value: "1" },
35 | { key: "staging", value: "1" },
36 | { key: "test", value: "1" },
37 | { key: "test_mode", value: "1" },
38 | { key: "testing", value: "1" },
39 | { key: "trace", value: "1" },
40 | { key: "validate", value: "1" },
41 | { key: "verbose", value: "1" },
42 | ];
43 |
44 | // Store modified URLs
45 | const modifiedUrls = new Set();
46 |
47 | // Function to append a specific query parameter to a URL
48 | function appendQueryParam(url, param) {
49 | const urlObj = new URL(url);
50 | urlObj.searchParams.set(param.key, param.value);
51 | return urlObj.href;
52 | }
53 |
54 |
55 | // Function to add a Query Params URL
56 | function addModifiedUrl(url) {
57 | if (!modifiedUrls.add(url)) {
58 | modifiedUrls.add(url);
59 | incrementCount();
60 |
61 | // Log for debugging
62 | console.log("addModifiedUrl: added url " + url + " to modified URLs");
63 | }
64 | }
65 |
66 | // Function to get Query Params URLs
67 | function getModifiedUrls() {
68 | return Array.from(modifiedUrls);
69 | }
70 |
71 | // Function to remove a specific found sensitive path
72 | function removeModifiedUrl(url) {
73 | modifiedUrls.delete(url);
74 | incrementCount();
75 |
76 | // Log for debugging
77 | console.log("removeModifiedUrl: removed url " + path + " from modified URLs");
78 | }
79 |
80 | // Function to clear Query Params URLs
81 | function clearModifiedUrls() {
82 | modifiedUrls.clear();
83 | chrome.browserAction.setBadgeText({ text: '' }); // Clear the badge text
84 |
85 | // Log for debugging
86 | console.log("clearModifiedUrls: cleared list of modified URLs");
87 | }
88 |
89 | // Function to fetch URL and compare responses with and without each parameter
90 | async function checkUrlWithParameters(url) {
91 | try {
92 | const originalResponse = await fetch(url);
93 | const originalText = await originalResponse.text();
94 |
95 | // Check all parameters combined
96 | const combinedUrl = queryParams.reduce((currentUrl, param) => {
97 | return appendQueryParam(currentUrl, param);
98 | }, url);
99 |
100 | // Add delay between requests
101 | await new Promise(resolve => setTimeout(resolve, 1000));
102 |
103 | const combinedResponse = await fetch(combinedUrl);
104 | const combinedText = await combinedResponse.text();
105 |
106 | if (await isDifferentResponse(originalText, combinedText)) {
107 | // Check each parameter individually
108 | for (const param of queryParams) {
109 | const modifiedUrl = appendQueryParam(url, param);
110 |
111 | const modifiedResponse = await fetch(modifiedUrl);
112 | const modifiedText = await modifiedResponse.text();
113 |
114 | if (await isDifferentResponse(originalText, modifiedText)) {
115 | console.log('%cParam query found: ' + modifiedUrl, 'background-color: green; color: white');
116 | addModifiedUrl(modifiedUrl);
117 | break;
118 | }
119 | }
120 | }
121 | } catch (error) {
122 | console.error(`Failed to fetch ${url}: ${error}`);
123 | }
124 | }
125 |
126 | // Expose functions to popup
127 | window.getModifiedUrls = getModifiedUrls;
128 | window.clearModifiedUrls = clearModifiedUrls;
129 |
130 | /*
131 | CUSTOM HEADERS
132 | */
133 |
134 | // List of custom headers to be tested
135 | const customHeaders = [
136 | { key: "Env", value: "pre" },
137 | { key: "X-Forwarded-For", value: "127.0.0.1" }
138 | ];
139 |
140 | const foundCustomHeaders = new Set();
141 |
142 | // Function add custom header
143 | function addCustomHeader(url, headerToAdd) {
144 | if (!headerToAdd || !headerToAdd.key || !headerToAdd.value) {
145 | console.error("Invalid header:", headerToAdd);
146 | return;
147 | }
148 | let headerString = url + " - " + headerToAdd.key + ": " + headerToAdd.value;
149 | foundCustomHeaders.add(headerString);
150 | incrementCount();
151 |
152 | console.log("addCustomHeader: added header " + headerToAdd.key + ": " + headerToAdd.value + " to found custom headers");
153 | }
154 |
155 | // Function to get the list of found custom headers
156 | function getCustomHeaders() {
157 | return foundCustomHeaders;
158 | }
159 |
160 | // Function to remove a custom header from the set of found headers
161 | function removeCustomHeader(headerToRemove) {
162 | // Remove the header directly, no need to loop through all headers
163 | if(foundCustomHeaders.has(headerToRemove)) {
164 | foundCustomHeaders.delete(headerToRemove);
165 | incrementCount();
166 |
167 | // Log for debugging
168 | console.log("removeCustomHeader: removed header " + headerToRemove + " from found custom headers");
169 | }
170 | }
171 |
172 | // Function to clear the list of found custom headers
173 | function clearCustomHeaders() {
174 | foundCustomHeaders.clear();
175 | chrome.browserAction.setBadgeText({ text: '' });
176 |
177 | // Log for debugging
178 | console.log("clearCustomHeaders: cleared list of found custom headers");
179 | }
180 |
181 | // Function to probe URL with custom headers
182 | async function probeUrlWithHeaders(url, headers) {
183 | let fetchHeaders = new Headers();
184 | for(let key in headers) {
185 | fetchHeaders.append(key, headers[key]);
186 | }
187 |
188 | let response;
189 | try {
190 | response = await fetch(url, { headers: fetchHeaders });
191 | } catch (err) {
192 | console.error(`Error fetching ${url}: ${err.message}`);
193 | return null;
194 | }
195 | return response.text();
196 | }
197 |
198 | // Function to test URL with each custom header
199 | async function probeHeaders(url) {
200 | const initialContent = await probeUrlWithHeaders(url, {});
201 |
202 | if (initialContent === null) return;
203 |
204 | let headers = {};
205 | customHeaders.forEach(header => {
206 | headers[header.key] = header.value;
207 | });
208 |
209 | const allHeadersContent = await probeUrlWithHeaders(url, headers);
210 |
211 | if (allHeadersContent === null) return;
212 |
213 | if (await isDifferentResponse(initialContent, allHeadersContent)) {
214 | for (let i = 0; i < customHeaders.length; i++) {
215 | let singleHeader = {};
216 | singleHeader[customHeaders[i].key] = customHeaders[i].value;
217 |
218 | const singleHeaderContent = await probeUrlWithHeaders(url, singleHeader);
219 |
220 | if (singleHeaderContent === null) continue;
221 |
222 | if (await isDifferentResponse(initialContent, singleHeaderContent)) {
223 | console.log('%cCustom header found in ' + url + ': ' + customHeaders[i].key + ": " + customHeaders[i].value, 'background-color: green; color: white');
224 | addCustomHeader(url, customHeaders[i]);
225 | break;
226 | }
227 | }
228 | }
229 | }
230 |
231 | // Expose function to popup
232 | window.getCustomHeaders = getCustomHeaders;
233 | window.removeCustomHeader = removeCustomHeader;
234 | window.clearCustomHeaders = clearCustomHeaders;
235 |
236 | /*
237 | SENSITIVE PATHS
238 | */
239 |
240 | // List of sensitive paths to be tested
241 | const sensitivePaths = [
242 | "/.git/config",
243 | "/.env", "/auth.json",
244 | "/config.json",
245 | "/bitbucket-pipelines.yml"
246 | ];
247 |
248 | // Store found sensitive paths
249 | const foundSensitivePaths = new Set();
250 |
251 | // Function to add a found sensitive path
252 | function addFoundSensitivePath(url) {
253 | if (!foundSensitivePaths.has(url)) {
254 | foundSensitivePaths.add(url);
255 | incrementCount();
256 |
257 | // Log for debugging
258 | console.log("addFoundSensitivePath: added path " + url + " to found sensitive paths");
259 | }
260 | }
261 |
262 | // Function to get found sensitive paths
263 | function getFoundSensitivePaths() {
264 | return Array.from(foundSensitivePaths);
265 | }
266 |
267 | // Function to remove a specific found sensitive path
268 | function removeSensitivePath(path) {
269 | foundSensitivePaths.delete(path);
270 | incrementCount();
271 |
272 | // Log for debugging
273 | console.log("getFoundSensitivePaths: returning list of found sensitive paths");
274 | }
275 |
276 | // Function to clear found sensitive paths
277 | function clearFoundSensitivePaths() {
278 | foundSensitivePaths.clear();
279 | chrome.browserAction.setBadgeText({ text: '' }); // Clear the badge text
280 |
281 | // Log for debugging
282 | console.log("clearFoundSensitivePaths: cleared list of found sensitive paths");
283 | }
284 |
285 | // Function to check sensitive paths for a domain
286 | async function checkSensitivePaths(url) {
287 | const urlObj = new URL(url);
288 | const domain = urlObj.protocol + '//' + urlObj.hostname;
289 |
290 | let originalResponse = await fetch(domain);
291 | let originalText = await originalResponse.text();
292 |
293 | for (let path of sensitivePaths) {
294 | let urlToCheck = domain + path;
295 |
296 | let response = await fetch(urlToCheck);
297 |
298 | if (response.status === 200) {
299 | let pathText = await response.text();
300 |
301 | // Check if the original response contains "soft 404" keywords
302 | let isSoft404 = [
303 | "no encontrado",
304 | "error 404",
305 | "página no existe",
306 | "no se pudo encontrar",
307 | "not found",
308 | "failed to connect to",
309 | ].some(keyword => pathText.toLowerCase().includes(keyword.toLowerCase()));
310 |
311 | if(await isDifferentResponse(originalText, pathText)) {
312 | if (isSoft404) {
313 | console.log('%cThe server responded with a status of 200, but it might be a "soft 404": ' + domain, 'background-color: yellow; color: black');
314 | } else {
315 | console.log('%cThe server responded with a status of 200: ' + response.url, 'background-color: green; color: white');
316 | addFoundSensitivePath(urlToCheck);
317 | }
318 | }
319 | }
320 | }
321 | }
322 |
323 | // Expose functions to popup
324 | window.getFoundSensitivePaths = getFoundSensitivePaths;
325 | window.removeSensitivePath = removeSensitivePath;
326 | window.clearFoundSensitivePaths = clearFoundSensitivePaths;
327 |
328 | /*
329 | GLOBAL FUNCTIONS
330 | */
331 |
332 | // Counter for the number of modified URLs and sensitive paths
333 | let countModifiedUrls = 0;
334 | let countSensitivePaths = 0;
335 |
336 | // Function to increment the counter and update the badge text
337 | function incrementCount() {
338 | countModifiedUrls = modifiedUrls.size;
339 | countCustomHeaders = foundCustomHeaders.size;
340 | countSensitivePaths = foundSensitivePaths.size;
341 |
342 | const totalCount = countModifiedUrls + countCustomHeaders + countSensitivePaths;
343 | chrome.browserAction.setBadgeText({ text: totalCount.toString() });
344 | chrome.browserAction.setBadgeBackgroundColor({ color: 'red' });
345 |
346 | // Log for debugging
347 | console.log("incrementCount: total count is now " + totalCount);
348 | }
349 |
350 | // Check if a hostname matches a pattern
351 | function matchesPattern(pattern, hostname) {
352 | const patternParts = pattern.split('.').reverse();
353 | const hostnameParts = hostname.split('.').reverse();
354 |
355 | // Check if the pattern is longer than the hostname. If so, it's not a match.
356 | if (patternParts.length > hostnameParts.length) {
357 | return false;
358 | }
359 |
360 | // Check each part of the pattern against the hostname.
361 | for (let i = 0; i < patternParts.length; i++) {
362 | if (patternParts[i] === '*') {
363 | return true; // Wildcard matches all remaining hostname parts.
364 | }
365 | if (patternParts[i] !== hostnameParts[i]) {
366 | return false; // Mismatch.
367 | }
368 | }
369 |
370 | return true;
371 | }
372 |
373 | // Check if a URL is in the whitelist
374 | async function isInWhitelist(url) {
375 | const urlObj = new URL(url);
376 | const { hostname } = urlObj;
377 |
378 | let storedSettings;
379 | try {
380 | storedSettings = await new Promise((resolve, reject) => {
381 | chrome.storage.sync.get('whitelist', (result) => {
382 | if (chrome.runtime.lastError) {
383 | reject(chrome.runtime.lastError);
384 | } else {
385 | resolve(result);
386 | }
387 | });
388 | });
389 | } catch (err) {
390 | console.error(err);
391 | }
392 |
393 | const whitelist = storedSettings.whitelist || [];
394 |
395 | for (let i = 0; i < whitelist.length; i++) {
396 | const pattern = whitelist[i];
397 | if (matchesPattern(pattern, hostname)) {
398 | return true;
399 | }
400 | }
401 |
402 | return false;
403 | }
404 |
405 | // Check if URL is valid
406 | async function isValidURL(url) {
407 | if (url.startsWith('chrome://')) {
408 | console.log("%cisValidUrl: skipping unsupported URL: " + url, 'background-color: yellow; color: black');
409 | return false;
410 | }
411 |
412 | let storedSettings;
413 | try {
414 | storedSettings = await new Promise((resolve, reject) => {
415 | chrome.storage.sync.get('checkInterval', (result) => {
416 | if (chrome.runtime.lastError) {
417 | reject(chrome.runtime.lastError);
418 | } else {
419 | resolve(result);
420 | }
421 | });
422 | });
423 | } catch (err) {
424 | console.error(err);
425 | }
426 | const check_interval = storedSettings.checkInterval * 60 * 1000 || 300 * 60 * 1000;
427 | console.log("isValidUrl: checking interval of " + storedSettings.checkInterval + " minutes");
428 |
429 | // Check when was the last time this URL was checked
430 | const lastChecked = localStorage.getItem(url);
431 | const now = Date.now();
432 |
433 | if (lastChecked !== null && (now - lastChecked < check_interval)) {
434 | console.log("%cisValidUrl: skipping recently checked URL: " + url, 'background-color: yellow; color: black');
435 | return true;
436 | }
437 |
438 | console.log("isValidUrl: url not analyzed in the lasts " + storedSettings.checkInterval + " minutes");
439 | localStorage.setItem(url, now.toString());
440 |
441 | return false;
442 | }
443 |
444 | // Check if URL is dynamic
445 | async function isDynamicContent(url) {
446 | console.log("isDynamicContent: checking if " + url + " is dynamic...");
447 | const checks = 4;
448 | let lastLength = null;
449 | let lastText = null;
450 | let totalDifference = 0;
451 |
452 | for (let i = 0; i < checks; i++) {
453 | let response = await fetch(url);
454 | let text = await response.text();
455 |
456 | let currentLength = text.length;
457 |
458 | if (lastLength !== null) {
459 | totalDifference += Math.abs(currentLength - lastLength);
460 | }
461 |
462 | if (lastLength && totalDifference > 150) {
463 | console.log("%cisDynamicContent: skipping dynamic url: " + url + ". Total difference is " + totalDifference, 'background-color: yellow; color: black');
464 |
465 | return true;
466 | } else if (lastText && await isDifferentResponseDynamic(lastText, text)) {
467 | console.log("%cisDynamicContent: skipping dynamic url: " + url + ". The similarity is under the threshold", 'background-color: yellow; color: black');
468 |
469 | return true;
470 | } else {
471 | console.log("isDynamicContent: not dynamic url: " + url + ". Total difference is " + totalDifference);
472 | }
473 |
474 | lastLength = currentLength;
475 | lastText = text;
476 |
477 | // Add delay between checks
478 | await new Promise(resolve => setTimeout(resolve, 1000));
479 | }
480 |
481 | return false;
482 | }
483 |
484 | // Function to check if two responses are meaningfully different
485 | async function isDifferentResponseDynamic(originalText, modifiedText) {
486 | // Calculate the similarity between the two responses
487 | const similarity = stringSimilarity.compareTwoStrings(originalText, modifiedText);
488 |
489 | const similarityThreshold = 0.97;
490 | console.log("isDifferentResponseDynamic: similarityThreshold is " + similarityThreshold + " and similarity is " + similarity);
491 |
492 | // Return true if the similarity is below the threshold
493 | return similarity < similarityThreshold;
494 | }
495 |
496 | // Function to check if two responses are meaningfully different
497 | async function isDifferentResponse(originalText, modifiedText) {
498 | // Calculate the similarity between the two responses
499 | const similarity = stringSimilarity.compareTwoStrings(originalText, modifiedText);
500 |
501 | let storedSettings;
502 | try {
503 | storedSettings = await new Promise((resolve, reject) => {
504 | chrome.storage.sync.get('similarityThreshold', (result) => {
505 | if (chrome.runtime.lastError) {
506 | reject(chrome.runtime.lastError);
507 | } else {
508 | resolve(result);
509 | }
510 | });
511 | });
512 | } catch (err) {
513 | console.error(err);
514 | }
515 | const similarityThreshold = storedSettings.similarityThreshold || 0.95;
516 | console.log("isDifferentResponse: similarityThreshold is " + similarityThreshold + " and similarity is " + similarity);
517 |
518 | // Return true if the similarity is below the threshold
519 | return similarity < similarityThreshold;
520 | }
521 |
522 | // Update the tabs onUpdated listener to also call checkSensitivePaths
523 | chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
524 | if (changeInfo.status === "complete") {
525 | const url = new URL(tab.url);
526 | url.hash = '';
527 | const sanitizedUrl = url.toString();
528 | const isInWhitelistResult = await isInWhitelist(sanitizedUrl);
529 | if (!isInWhitelistResult) {
530 | console.log("%c[+] LAUNCHING DEBUGHUNTERPRO ON " + sanitizedUrl, 'background-color: purple; color: white');
531 | try {
532 | // Check if valid URL
533 | if (await isValidURL(sanitizedUrl)) {
534 | return;
535 | } else {
536 | // Skip dynamic content
537 | if (await isDynamicContent(sanitizedUrl)) {
538 | return;
539 | } else {
540 | await checkUrlWithParameters(sanitizedUrl, queryParams);
541 | await probeHeaders(sanitizedUrl);
542 | }
543 | await checkSensitivePaths(sanitizedUrl);
544 | }
545 | } catch (error) {
546 | console.error("Error processing URL " + tab.url + ": " + error, "background-color: red; color: white");
547 | }
548 | } else {
549 | console.log("%cURL whitelisted, not making requests: " + sanitizedUrl, "background-color: yellow; color: black");
550 | }
551 | }
552 | });
553 |
554 |
--------------------------------------------------------------------------------
/images/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devploit/debugHunter/e8aad2bd69043598502b541c2e01b7b98ab07440/images/banner.png
--------------------------------------------------------------------------------
/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devploit/debugHunter/e8aad2bd69043598502b541c2e01b7b98ab07440/images/icon.png
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "debugHunter",
4 | "version": "1.1.1",
5 | "description": "Discover hidden debugging parameters/headers and uncover web application secrets",
6 | "options_page": "options.html",
7 | "icons": {
8 | "48": "images/icon.png"
9 | },
10 | "permissions": [
11 | "webRequest",
12 | "webRequestBlocking",
13 | "storage",
14 | "",
15 | "tabs"
16 | ],
17 | "background": {
18 | "scripts": ["similarity.min.js", "debugHunter.js"],
19 | "persistent": true
20 | },
21 | "browser_action": {
22 | "default_icon": "images/icon.png",
23 | "default_popup": "popup.html"
24 | }
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/options.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
83 | Options
84 |
85 |
86 |
Options
87 |
88 |
89 |
Current value: 0.95
90 |
The similarity threshold determines the minimum similarity score required for a pair of URLs to be considered the same. The value ranges from 0 to 1, where 0 means that any pair of URLs is considered the same, and 1 means that only identical URLs are considered the same. The closer the value is to 1, the more similar the URLs need to be to be considered the same.
91 |
Default value is: 0.95
92 |
93 |
94 |
Current value: 480
95 |
Check Interval determines the frequency at which the extension checks for changes on the websites you visit. It is expressed in minutes, with a minimum of 1 minute and a maximum of 5000 minutes. Adjusting the slider will immediately save the new interval to your settings. Note: Lower intervals may impact performance.
96 |
Default value is: 480 minutes (8 hours)
97 |
98 |
99 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/options.js:
--------------------------------------------------------------------------------
1 | document.addEventListener('DOMContentLoaded', () => {
2 | // Get the similarity threshold slider and display elements
3 | const similarityThresholdSlider = document.getElementById('similarityThreshold');
4 | const similarityThresholdValue = document.getElementById('similarityThresholdValue');
5 |
6 | // Function to update the display value
7 | function updateDisplayValue(value, displayElement) {
8 | displayElement.textContent = value;
9 | }
10 |
11 | // Load the saved similarity threshold value
12 | chrome.storage.sync.get('similarityThreshold', (data) => {
13 | const value = data.similarityThreshold || 0.95;
14 | similarityThresholdSlider.value = value;
15 | updateDisplayValue(value, similarityThresholdValue);
16 | });
17 |
18 | // Save the similarity threshold value when the slider changes
19 | similarityThresholdSlider.addEventListener('input', (e) => {
20 | const value = e.target.value;
21 | updateDisplayValue(value, similarityThresholdValue);
22 | chrome.storage.sync.set({ similarityThreshold: value }, () => {
23 | console.log('Similarity threshold saved:', value);
24 | });
25 | });
26 |
27 | // Get the check interval slider and display elements
28 | const checkIntervalRange = document.getElementById('checkInterval');
29 | const checkIntervalValue = document.getElementById('checkIntervalValue');
30 |
31 | // Load the saved check interval value
32 | chrome.storage.sync.get('checkInterval', (data) => {
33 | const value = data.checkInterval || 480;
34 | checkIntervalRange.value = value;
35 | updateDisplayValue(value, checkIntervalValue);
36 | });
37 |
38 | // Save the check interval value when the slider changes
39 | checkIntervalRange.addEventListener('input', (e) => {
40 | const value = e.target.value;
41 | updateDisplayValue(value, checkIntervalValue);
42 | chrome.storage.sync.set({ checkInterval: value }, () => {
43 | console.log('Check interval saved:', value);
44 | });
45 | });
46 |
47 | // Get the whitelist display, form, and input elements
48 | const whitelistDisplay = document.getElementById('whitelistDisplay');
49 | const addWhitelistForm = document.getElementById('addWhitelistForm');
50 | const newWhitelistDomain = document.getElementById('newWhitelistDomain');
51 |
52 | // Load the saved whitelist
53 | chrome.storage.sync.get('whitelist', (data) => {
54 | const whitelist = data.whitelist || [];
55 | displayWhitelist(whitelist);
56 | });
57 |
58 | // Display the whitelist
59 | function displayWhitelist(whitelist) {
60 | // Clear the current display
61 | whitelistDisplay.innerHTML = '';
62 |
63 | // Add each domain to the display
64 | whitelist.forEach(domain => {
65 | const listItem = document.createElement('li');
66 | listItem.textContent = domain;
67 | const removeButton = document.createElement('button');
68 | removeButton.textContent = 'Remove';
69 | removeButton.className = 'remove';
70 | removeButton.addEventListener('click', () => {
71 | removeDomainFromWhitelist(domain);
72 | });
73 | listItem.appendChild(removeButton);
74 | whitelistDisplay.appendChild(listItem);
75 | });
76 | }
77 |
78 | // Remove a domain from the whitelist
79 | function removeDomainFromWhitelist(domain) {
80 | chrome.storage.sync.get('whitelist', (data) => {
81 | let whitelist = data.whitelist || [];
82 | whitelist = whitelist.filter(d => d !== domain);
83 | chrome.storage.sync.set({ whitelist: whitelist }, () => {
84 | console.log('Domain removed from whitelist:', domain);
85 | displayWhitelist(whitelist);
86 | });
87 | });
88 | }
89 |
90 | // Add a new domain to the whitelist when the form is submitted
91 | addWhitelistForm.addEventListener('submit', (e) => {
92 | e.preventDefault();
93 | const domain = newWhitelistDomain.value.trim();
94 | if (domain) {
95 | chrome.storage.sync.get('whitelist', (data) => {
96 | let whitelist = data.whitelist || [];
97 | if (!whitelist.includes(domain)) {
98 | whitelist.push(domain);
99 | chrome.storage.sync.set({ whitelist: whitelist }, () => {
100 | console.log('Domain added to whitelist:', domain);
101 | displayWhitelist(whitelist);
102 | });
103 | }
104 | });
105 | newWhitelistDomain.value = '';
106 | }
107 | });
108 | });
109 |
--------------------------------------------------------------------------------
/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
Sensitive Paths
93 |
94 |
Custom Headers
95 |
96 |
Query Params
97 |
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/popup.js:
--------------------------------------------------------------------------------
1 | // QUERY PARAMS
2 |
3 | // This function populates the modified URLs list in the popup
4 | function updateModifiedUrlsList() {
5 | const modifiedUrls = chrome.extension.getBackgroundPage().getModifiedUrls();
6 | const list = document.getElementById("queryParams");
7 |
8 | list.innerHTML = "";
9 |
10 | // Iterate over each modified URL
11 | for (const url of modifiedUrls) {
12 | // Create a new list item
13 | const listItem = document.createElement("li");
14 | listItem.style.position = "relative";
15 |
16 | // Create a new anchor tag
17 | const link = document.createElement("a");
18 | link.href = url;
19 | link.target = "_blank";
20 |
21 | // Trim the displayed URL if it is too long
22 | if (url.length > 60) {
23 | link.textContent = url.substring(0, 57) + "...";
24 | } else {
25 | link.textContent = url;
26 | }
27 |
28 | listItem.appendChild(link);
29 |
30 | // Create the remove icon
31 | const removeIcon = document.createElement('i');
32 | removeIcon.className = "fa fa-times";
33 | removeIcon.style.position = "absolute";
34 | removeIcon.style.right = "10px";
35 | removeIcon.style.top = "50%";
36 | removeIcon.style.transform = "translateY(-50%)";
37 | removeIcon.style.cursor = "pointer";
38 | removeIcon.onclick = function() {
39 | // Remove the URL from the modified URLs list in the background page
40 | chrome.extension.getBackgroundPage().removeModifiedUrl(url);
41 | updateModifiedUrlsList();
42 | }
43 | // Add the remove icon to the list item
44 | listItem.appendChild(removeIcon);
45 |
46 | // Add the list item to the list
47 | list.appendChild(listItem);
48 | }
49 | }
50 |
51 | // Call updateModifiedUrlsList when the popup is loaded
52 | document.addEventListener("DOMContentLoaded", updateModifiedUrlsList);
53 |
54 | // CUSTOM HEADERS
55 | function updateCustomHeadersList() {
56 | const foundCustomHeaders = chrome.extension.getBackgroundPage().getCustomHeaders();
57 | const list = document.getElementById("customHeaders");
58 |
59 | list.innerHTML = "";
60 |
61 | for (const header of foundCustomHeaders) {
62 | const listItem = document.createElement("li");
63 | listItem.style.position = "relative";
64 |
65 | const textNode = document.createTextNode(header);
66 | listItem.appendChild(textNode);
67 |
68 | const removeIcon = document.createElement('i');
69 | removeIcon.className = "fa fa-times";
70 | removeIcon.style.position = "absolute";
71 | removeIcon.style.right = "10px";
72 | removeIcon.style.top = "50%";
73 | removeIcon.style.transform = "translateY(-50%)";
74 | removeIcon.style.cursor = "pointer";
75 | removeIcon.onclick = function() {
76 | // Remove the URL from the modified URLs list in the background page
77 | chrome.extension.getBackgroundPage().removeCustomHeader(header);
78 | updateCustomHeadersList();
79 | }
80 | // Add the remove icon to the list item
81 | listItem.appendChild(removeIcon);
82 |
83 | // Add the list item to the list
84 | list.appendChild(listItem);
85 | }
86 | }
87 |
88 | // Call updateCustomHeadersList when the popup is loaded
89 | document.addEventListener("DOMContentLoaded", updateCustomHeadersList);
90 |
91 | // SENSITIVE PATHS
92 | function updateSensitivePaths() {
93 | const urlList = document.getElementById('sensitivePaths');
94 | while (urlList.firstChild) {
95 | urlList.firstChild.remove();
96 | }
97 |
98 | // Get updated found sensitive paths
99 | const paths = chrome.extension.getBackgroundPage().getFoundSensitivePaths();
100 |
101 | // Iterate over each found sensitive path
102 | for (let path of paths) {
103 | // Create a new list item
104 | const listItem = document.createElement('li');
105 | listItem.style.position = "relative";
106 |
107 | // Create a new anchor tag
108 | const anchor = document.createElement('a');
109 | anchor.href = path;
110 | anchor.target = '_blank';
111 | anchor.textContent = path;
112 | listItem.appendChild(anchor);
113 |
114 | // Create the remove icon
115 | const removeIcon = document.createElement('i');
116 | removeIcon.className = "fa fa-times";
117 | removeIcon.style.position = "absolute";
118 | removeIcon.style.right = "10px";
119 | removeIcon.style.top = "50%";
120 | removeIcon.style.transform = "translateY(-50%)";
121 | removeIcon.style.cursor = "pointer";
122 | removeIcon.onclick = function() {
123 | // Remove the path from the found sensitive paths list in the background page
124 | chrome.extension.getBackgroundPage().removeSensitivePath(path);
125 | updateSensitivePaths();
126 | }
127 | // Add the remove icon to the list item
128 | listItem.appendChild(removeIcon);
129 |
130 | // Add the list item to the list
131 | urlList.appendChild(listItem);
132 | }
133 | }
134 |
135 | // Call updateSensitivePaths when the popup is loaded
136 | document.addEventListener("DOMContentLoaded", updateSensitivePaths);
137 |
138 | // Get the current options from storage
139 | document.getElementById('info-icon').addEventListener('click', () => {
140 | chrome.tabs.create({ url: 'https://github.com/devploit/debugHunterPro' });
141 | });
142 |
143 | // Open the options page when the options link is clicked
144 | document.getElementById('options-link').addEventListener('click', () => {
145 | chrome.runtime.openOptionsPage();
146 | });
147 |
148 | // Clear all found sensitive paths, custom headers, and modified URLs
149 | document.getElementById('clear-all').addEventListener('click', () => {
150 | chrome.extension.getBackgroundPage().clearFoundSensitivePaths();
151 | updateSensitivePaths();
152 | chrome.extension.getBackgroundPage().clearCustomHeaders();
153 | updateCustomHeadersList();
154 | chrome.extension.getBackgroundPage().clearModifiedUrls();
155 | updateModifiedUrlsList();
156 | });
157 |
--------------------------------------------------------------------------------
/similarity.min.js:
--------------------------------------------------------------------------------
1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.stringSimilarity=e():t.stringSimilarity=e()}(self,(function(){return t={138:t=>{function e(t,e){if((t=t.replace(/\s+/g,""))===(e=e.replace(/\s+/g,"")))return 1;if(t.length<2||e.length<2)return 0;let r=new Map;for(let e=0;e0&&(r.set(o,s-1),n++)}return 2*n/(t.length+e.length-2)}t.exports={compareTwoStrings:e,findBestMatch:function(t,r){if(!function(t,e){return"string"==typeof t&&!!Array.isArray(e)&&!!e.length&&!e.find((function(t){return"string"!=typeof t}))}(t,r))throw new Error("Bad arguments: First argument should be a string, second should be an array of strings");const n=[];let o=0;for(let s=0;sn[o].rating&&(o=s)}return{ratings:n,bestMatch:n[o],bestMatchIndex:o}}}}},e={},function r(n){if(e[n])return e[n].exports;var o=e[n]={exports:{}};return t[n](o,o.exports,r),o.exports}(138);var t,e}));
2 |
--------------------------------------------------------------------------------