├── 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 | [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](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 |
100 | 101 | 102 |
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 | --------------------------------------------------------------------------------