├── LICENSE ├── README.md └── DocuFinder.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 weekndr_sec 4 | Copyright (c) 2025 k2sosint 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DocuFinderJS 2 | 3 | ![docufinderjs_apr25](https://github.com/user-attachments/assets/fc97dce3-ebab-4d48-9d2b-69e72faa31fc) 4 | 5 | **DocuFinder** is an external attack surface monitoring (EASM) tool that automates traditional OSINT techniques to find externally accessible documents within a target domain. 6 | Protect your organization, improve your penetration tests, increase your bug bounty revenue, & more. 7 | 8 | # Disclaimer 9 | **DocuFinder is intended for authorized use only**. 10 | 11 | - If you are an investigator or open-source intelligence professional, ensure you have proper jurisdiction prior to accessing results. 12 | 13 | - For external penetration tests & bug bounty work, only access scan results after verifying authorization from the target domain. 14 | - i.e: *Is the URL I found in-scope of my penetration test or investigation?* 15 | - By running a scan, this is passive reconnaissance. However, opening links contained in scan results is an active engagement. 16 | 17 | - These could be files containing sensitive info & downloaded directly to your machine on-access. 18 | 19 | - **I am not responsible for any legal or criminal proceedings filed against you for using this tool**. 20 | 21 | # Getting Started 22 | To get started with the DocuFinderJS bookmarklet, perform the following: 23 | 24 | 1. **Open the source code in any text editor**. 25 | 26 | 2. **Highlight the source code & copy. No need to make any changes**. 27 | 28 | 3. **Open your browser of choice**. 29 | 30 | 4. **Create a bookmark in your browser's bookmark bar**. 31 | - You'll want to set the bar to always appear. 32 | 33 | 5. **Paste the bookmarklet in the URL section**. 34 | - To validate, press the "HOME" key after pasting & verify the entry begins with "javascript:". 35 | 36 | 6. **Create a name for the bookmarklet**. 37 | - I recommend setting this to the name included with the release, such as "DocuFinderJS v1.3" This way, when I release updates, you can easily verify if you are running the latest release. 38 | 39 | 7. **Create a new tab and click on the bookmarklet**. 40 | - I recommend running this in a new tab in a dedicated browser for these tools, since you'll have to disable pop-ups. This is only to open windows containing your search results, nothing more. 41 | 42 | 8. **Enter your target domain in the prompt**. 43 | - If you are a penetration tester, this could be a client you are performing passive reconnaissance on. 44 | - If you are working on a bug bounty program, the same would apply when this is authorized & in-scope. 45 | - If you are a cybersecurity analyst or information security officer, this might be your employer's domain. 46 | 47 | 9. **Review your results & enjoy**. 48 | - Once again, please verify that the domain containing the files is in-scope for the project you are supporting prior to access. 49 | 50 | # External Links 51 | 52 | - For more info on using JavaScript bookmarklets, check out this guide. 53 | - [**Installing Bookmarklets - mreidsma.github.io**](https://mreidsma.github.io/bookmarklets/installing.html) 54 | -------------------------------------------------------------------------------- /DocuFinder.js: -------------------------------------------------------------------------------- 1 | javascript:(() => { 2 | 3 | if (document.getElementById('dfj-host')) { 4 | document.getElementById('dfj-host').remove(); 5 | } 6 | 7 | const STYLES = ` 8 | :host { all: initial; } 9 | .dfj-wrap{position:fixed;top:16px;right:16px;z-index:2147483647;font-family:system-ui,Segoe UI,Roboto,Arial,sans-serif} 10 | .card{background:#fff;border:1px solid #d9e2ec;border-radius:12px;box-shadow:0 10px 28px rgba(0,0,0,.12);padding:14px;min-width:340px} 11 | .title{font-weight:700;font-size:14px;margin:0 0 8px 0} 12 | .row{display:flex;gap:8px;flex-wrap:wrap;margin-top:6px} 13 | .group{border:1px solid #eef2f6;border-radius:10px;padding:8px;margin-top:8px} 14 | .group legend{font-size:12px;font-weight:600;padding:0 4px;color:#334e68} 15 | label{font-size:12px;display:inline-flex;align-items:center;gap:6px;margin:4px 8px 4px 0} 16 | input[type=text],input[type=number]{width:100%;padding:8px;border:1px solid #cbd5e1;border-radius:8px;font-size:12px} 17 | .actions{display:flex;gap:8px;margin-top:10px;flex-wrap:wrap} 18 | button{border:0;border-radius:10px;padding:8px 10px;font-weight:600;cursor:pointer} 19 | .run{background:#0b69ff;color:#fff} 20 | .cancel{background:#e2e8f0} 21 | .tiny{font-size:11px;color:#475569;margin-top:6px} 22 | `; 23 | 24 | const FT = [ 25 | {key:"pdf", label:"PDF"}, 26 | {key:"ppt", label:"PPT/PPTX"}, 27 | {key:"doc", label:"DOC/DOCX"}, 28 | {key:"xls", label:"XLS/XLSX/CSV"}, 29 | {key:"txt", label:"TXT/XML/RTF"}, 30 | ]; 31 | const ENGS = [ 32 | {key:"google", label:"Google"}, 33 | {key:"bing", label:"Bing"}, 34 | {key:"ddg", label:"DuckDuckGo"}, 35 | ]; 36 | const FT_QUERIES = { 37 | pdf:"filetype:pdf OR ext:pdf", 38 | ppt:"filetype:ppt OR ext:ppt OR filetype:pptx OR ext:pptx", 39 | doc:"filetype:doc OR ext:doc OR filetype:docx OR ext:docx", 40 | xls:"filetype:xls OR ext:xls OR filetype:xlsx OR ext:xlsx OR filetype:csv OR ext:csv", 41 | txt:"filetype:txt OR ext:txt OR filetype:xml OR ext:xml OR filetype:rtf OR ext:rtf" 42 | }; 43 | const ENGINES = { 44 | google:q=>`https://www.google.com/search?q=${encodeURIComponent(q)}&filter=0`, 45 | bing:q=>`https://www.bing.com/search?q=${encodeURIComponent(q)}`, 46 | ddg:q=>`https://duckduckgo.com/?q=${encodeURIComponent(q)}`, 47 | }; 48 | 49 | const STORE_KEY = 'dfj-settings-v3'; 50 | const loadSettings = () => { try { return JSON.parse(localStorage.getItem(STORE_KEY) || '{}'); } catch { return {}; } }; 51 | const saveSettings = (obj) => { try { localStorage.setItem(STORE_KEY, JSON.stringify(obj)); } catch {} }; 52 | 53 | const host = document.createElement('div'); 54 | host.id = 'dfj-host'; 55 | const root = host.attachShadow({mode:'closed'}); 56 | const style = document.createElement('style'); 57 | style.textContent = STYLES; 58 | const wrap = document.createElement('div'); 59 | wrap.className = 'dfj-wrap'; 60 | wrap.innerHTML = ` 61 | 92 | `; 93 | root.appendChild(style); 94 | root.appendChild(wrap); 95 | document.body.appendChild(host); 96 | 97 | const $ = sel => root.querySelector(sel); 98 | const ftRow = $('#ftRow'); 99 | const engRow = $('#engRow'); 100 | 101 | function mkCheck(id, checked=true) { 102 | const i = document.createElement('input'); 103 | i.type = 'checkbox'; i.id = id; i.checked = checked; 104 | return i; 105 | } 106 | FT.forEach(({key,label}) => { 107 | const lab = document.createElement('label'); 108 | const cb = mkCheck(`ft-${key}`, true); 109 | cb.dataset.ft = key; 110 | lab.appendChild(cb); lab.append(label); 111 | ftRow.appendChild(lab); 112 | }); 113 | ENGS.forEach(({key,label}) => { 114 | const lab = document.createElement('label'); 115 | const cb = mkCheck(`eng-${key}`, true); 116 | cb.dataset.eng = key; 117 | lab.appendChild(cb); lab.append(label); 118 | engRow.appendChild(lab); 119 | }); 120 | 121 | const saved = loadSettings(); 122 | if (saved.domain) $('#domain').value = saved.domain; 123 | if (Number.isFinite(saved.delay)) $('#delay').value = String(saved.delay); 124 | if (Number.isFinite(saved.maxTabs)) $('#maxTabs').value = String(saved.maxTabs); 125 | if (typeof saved.includeSubs === 'boolean') $('#subs').checked = !!saved.includeSubs; 126 | if (Array.isArray(saved.fts)) { 127 | Array.from(root.querySelectorAll('input[data-ft]')).forEach(i => { i.checked = saved.fts.includes(i.dataset.ft); }); 128 | } 129 | if (Array.isArray(saved.engs)) { 130 | Array.from(root.querySelectorAll('input[data-eng]')).forEach(i => { i.checked = saved.engs.includes(i.dataset.eng); }); 131 | } 132 | 133 | function closePanel() { host.remove(); window.removeEventListener('keydown', onKey); } 134 | function onKey(e){ if(e.key === 'Escape') closePanel(); } 135 | window.addEventListener('keydown', onKey); 136 | 137 | function buildSiteTargets(domainRaw, includeSubs) { 138 | const core = domainRaw; 139 | const targets = new Set([core]); 140 | if (includeSubs) { 141 | const common = ["*"]; 142 | common.forEach(s => targets.add(`${s}.${core}`)); 143 | } 144 | return Array.from(targets); 145 | } 146 | 147 | function buildUrls(domainRaw, includeSubs, fts, engs) { 148 | const sites = buildSiteTargets(domainRaw, includeSubs); 149 | const siteClause = sites.length === 1 150 | ? `site:${sites[0]}` 151 | : `(${sites.map(s => 'site:' + s).join(' OR ')})`; 152 | 153 | const urls = []; 154 | for (const ft of fts) { 155 | const q = `(${FT_QUERIES[ft]}) ${siteClause}`; 156 | for (const e of engs) { 157 | const b = ENGINES[e]; 158 | if (b) urls.push(b(q)); 159 | } 160 | } 161 | return urls; 162 | } 163 | 164 | function currentSelections() { 165 | const domainRaw = $('#domain').value.trim().replace(/^https?:\/\//i,'').replace(/\/+$/,''); 166 | const includeSubs = $('#subs').checked; 167 | const delay = Math.max(0, parseInt($('#delay').value || '500', 10)); 168 | const maxTabs = Math.max(0, parseInt($('#maxTabs').value || '0', 10)); 169 | const fts = Array.from(root.querySelectorAll('input[type=checkbox][data-ft]')) 170 | .filter(i=>i.checked).map(i=>i.dataset.ft); 171 | const engs = Array.from(root.querySelectorAll('input[type=checkbox][data-eng]')) 172 | .filter(i=>i.checked).map(i=>i.dataset.eng); 173 | return { domainRaw, includeSubs, delay, maxTabs, fts, engs }; 174 | } 175 | 176 | function persist({domainRaw, includeSubs, delay, maxTabs, fts, engs}) { 177 | saveSettings({ domain: domainRaw, includeSubs, delay, maxTabs, fts, engs }); 178 | } 179 | 180 | function run() { 181 | try { 182 | const sel = currentSelections(); 183 | const {domainRaw, includeSubs, delay, maxTabs, fts, engs} = sel; 184 | if (!domainRaw) { alert('Please enter a domain (e.g., example.com)'); return; } 185 | if (!fts.length){ alert('Select at least one filetype'); return; } 186 | if (!engs.length){ alert('Select at least one search engine'); return; } 187 | 188 | persist(sel); 189 | 190 | const urls = buildUrls(domainRaw, includeSubs, fts, engs); 191 | const list = (maxTabs && maxTabs>0) ? urls.slice(0, maxTabs) : urls; 192 | if (!list.length) { alert('Nothing to open.'); return; } 193 | 194 | list.forEach((u,i)=>setTimeout(()=>window.open(u,'_blank'), i*delay)); 195 | closePanel(); 196 | } catch (err) { 197 | alert('DocuFinderJS error: ' + (err && err.message ? err.message : err)); 198 | } 199 | } 200 | 201 | $('#runBtn').addEventListener('click', run); 202 | $('#cancelBtn').addEventListener('click', closePanel); 203 | })(); 204 | --------------------------------------------------------------------------------