<!DOCTYPE html><htmllang="zh-CN"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1.0"> <title>网站后台扫描工具 v2</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif; background: linear-gradient(135deg, #0f0c29, #302b63, #24243e); min-height: 100vh; padding: 20px; color: #e0e0e0; } .container { max-width: 960px; margin: 0 auto; background: rgba(255, 255, 255, 0.06); backdrop-filter: blur(12px); border-radius: 16px; padding: 30px; border: 1px solid rgba(255,255,255,0.1); box-shadow: 0 8px 32px rgba(0,0,0,0.4); } h1 { text-align: center; font-size: 28px; margin-bottom: 8px; background: linear-gradient(90deg, #00d2ff, #3a7bd5); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } .subtitle { text-align: center; color: #888; margin-bottom: 20px; font-size: 14px; } .warning-box { background: rgba(255, 152, 0, 0.12); border: 1px solid rgba(255, 152, 0, 0.25); border-radius: 8px; padding: 12px 16px; margin-bottom: 16px; font-size: 13px; color: #ffb74d; line-height: 1.6; } .warning-box strong { color: #ffcc80; } .input-group { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 12px; } .input-group label { display: block; margin-bottom: 4px; color: #aaa; font-size: 13px; } .input-group input, .input-group select { width: 100%; padding: 10px 14px; background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.15); border-radius: 8px; color: #fff; font-size: 14px; transition: border-color 0.3s; } .input-group input:focus { outline: none; border-color: #3a7bd5; } .full-width { grid-column: 1 / -1; } textarea { width: 100%; height: 140px; padding: 12px; background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.12); border-radius: 8px; color: #ccc; font-size: 13px; font-family: 'Consolas', 'Courier New', monospace; resize: vertical; transition: border-color 0.3s; } textarea:focus { outline: none; border-color: #3a7bd5; } .btn-row { display: flex; gap: 10px; margin: 14px 0; flex-wrap: wrap; } .btn { padding: 10px 28px; border: none; border-radius: 8px; font-size: 15px; font-weight: 600; cursor: pointer; transition: all 0.3s; flex: 1; min-width: 100px; } .btn-primary { background: linear-gradient(135deg, #00d2ff, #3a7bd5); color: #fff; } .btn-primary:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(0,210,255,0.3); } .btn-primary:disabled { opacity: 0.5; cursor: not-allowed; transform: none; } .btn-danger { background: linear-gradient(135deg, #ff416c, #ff4b2b); color: #fff; } .btn-danger:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(255,65,108,0.3); } .btn-danger:disabled { opacity: 0.5; cursor: not-allowed; } .btn-secondary { background: rgba(255,255,255,0.1); color: #ccc; border: 1px solid rgba(255,255,255,0.15); } .btn-secondary:hover { background: rgba(255,255,255,0.18); } .status-bar { display: flex; justify-content: space-between; align-items: center; padding: 10px 14px; background: rgba(255,255,255,0.05); border-radius: 8px; margin-bottom: 10px; font-size: 13px; flex-wrap: wrap; gap: 6px; } .progress-wrap { width: 100%; height: 6px; background: rgba(255,255,255,0.1); border-radius: 3px; margin-bottom: 10px; overflow: hidden; } .progress-bar { height: 100%; width: 0%; background: linear-gradient(90deg, #00d2ff, #3a7bd5); border-radius: 3px; transition: width 0.3s; } #resultArea { margin-top: 10px; border: 1px solid rgba(255,255,255,0.08); border-radius: 8px; overflow: hidden; } .result-header { display: flex; justify-content: space-between; padding: 10px 14px; background: rgba(255,255,255,0.05); font-size: 13px; color: #aaa; border-bottom: 1px solid rgba(255,255,255,0.06); } .result-list { max-height: 400px; overflow-y: auto; padding: 4px 0; } .result-item { display: flex; justify-content: space-between; align-items: center; padding: 7px 14px; font-size: 13px; font-family: 'Consolas', 'Courier New', monospace; border-bottom: 1px solid rgba(255,255,255,0.03); transition: background 0.2s; gap: 8px; } .result-item:hover { background: rgba(255,255,255,0.04); } .result-item .url { color: #4fc3f7; word-break: break-all; flex: 1; min-width: 0; } .result-item .status { font-weight: 700; margin: 0 6px; white-space: nowrap; } .status-200 { color: #66bb6a; } .status-301, .status-302 { color: #ffa726; } .status-403 { color: #ef5350; } .status-500 { color: #ab47bc; } .status-opaque { color: #4dd0e1; } .status-error { color: #ef5350; } .result-item .size { color: #888; white-space: nowrap; } .no-results { text-align: center; padding: 30px; color: #666; } .badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; } #logArea { margin-top: 10px; max-height: 120px; overflow-y: auto; background: rgba(0,0,0,0.2); border-radius: 6px; padding: 8px 12px; font-size: 12px; font-family: 'Consolas', monospace; color: #888; line-height: 1.5; display: none; } #logArea .log-error { color: #ef5350; } #logArea .log-info { color: #4fc3f7; } #logArea .log-warn { color: #ffa726; } ::-webkit-scrollbar { width: 6px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.15); border-radius: 3px; } .method-toggle { display: flex; gap: 10px; align-items: center; margin-bottom: 8px; } .method-toggle label { color: #aaa; font-size: 13px; } .method-toggle select { padding: 6px 10px; background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.15); border-radius: 6px; color: #fff; font-size: 13px; } </style></head><body> <divclass="container"> <h1>🔍 网站后台扫描工具 v2</h1> <pclass="subtitle">基于浏览器 Fetch API — 修复跨域问题</p> <divclass="warning-box"> <strong>⚠️ 跨域说明:</strong> 扫描<strong>跨域</strong>网站时,浏览器无法获取具体HTTP状态码(显示为 <strong>opaque</strong>), 但可以判断路径<strong>是否存在</strong>(能连通即视为存在)。<br> 扫描<strong>同域</strong>网站时可显示完整状态码和响应大小。 </div> <divclass="input-group"> <div> <label>目标URL</label> <inputtype="url"id="targetUrl"value="http://example.com"placeholder="http://example.com"> </div> <div> <label>并发数</label> <selectid="concurrency"> <optionvalue="3">3</option> <optionvalue="5"selected>5</option> <optionvalue="10">10</option> <optionvalue="15">15</option> <optionvalue="20">20</option> <optionvalue="30">30</option> </select> </div> </div> <divclass="method-toggle"> <label>请求方式:</label> <selectid="httpMethod"> <optionvalue="HEAD">HEAD(更快,仅获取头)</option> <optionvalue="GET"selected>GET(获取内容,更准确)</option> </select> <labelstyle="margin-left:16px;"> <inputtype="checkbox"id="showErrors"checked> 显示连接错误 </label> <labelstyle="margin-left:12px;"> <inputtype="checkbox"id="showLog"onchange="toggleLog()"> 显示日志 </label> </div> <labelstyle="display:block;margin-bottom:6px;color:#aaa;font-size:13px;">路径字典(每行一个)</label> <textareaid="wordlist"placeholder="输入要扫描的路径,每行一个...">adminloginadmin/login.phpadmin/login.htmladmin/index.phpadmin/index.htmlmanagemanagermanagementbackenddashboardwp-adminadministratoradmin123rootsystemcontrolpanelcpanelcmsadmin/admin.phpadmin/manage.phpadmin/dashboard.phpuseruser/login.phpregistersignupapiapi/loginapi/v1api/v2testphpmyadminphpMyAdminmysqladminersetupconfiginstallbackupsqldatabaseuploaduploadsfilesfiledownload.git.git/config.env.svnrobots.txtsitemap.xmlcrossdomain.xmlindex.phpindex.htmldefault.aspxweb.config.htaccesshtaccess.txtconfig.phpconfig.inc.phpdb.phpdb.inc.phpconn.phpconn.inc.phpadmin/uploadadmin/upload.phpadmin/fileadmin/file.phpadmin/backupadmin/backup.phpadmin/databaseadmin/database.phpadmin/configadmin/config.phpadmin/useradmin/user.phpadmin/user_list.phpadmin/roleadmin/role.phpadmin/menuadmin/menu.phpadmin/settingadmin/setting.phpadmin/logout.php</textarea> <divclass="btn-row"> <buttonclass="btn btn-primary"id="btnStart"onclick="startScan()">🚀 开始扫描</button> <buttonclass="btn btn-danger"id="btnStop"onclick="stopScan()"disabled>⏹ 停止</button> <buttonclass="btn btn-secondary"onclick="clearResults()">🗑 清空结果</button> <buttonclass="btn btn-secondary"onclick="exportResults()">📥 导出结果</button> <buttonclass="btn btn-secondary"onclick="loadDefaultDict()">📖 加载默认字典</button> </div> <divclass="progress-wrap"> <divclass="progress-bar"id="progressBar"></div> </div> <divclass="status-bar"> <spanid="statusText">就绪</span> <spanid="statsText">已扫描: 0 / 0 | 发现: 0 | 错误: 0</span> </div> <divid="resultArea"> <divclass="result-header"> <span>URL</span> <span>状态码 / 大小</span> </div> <divclass="result-list"id="resultList"> <divclass="no-results">等待扫描...</div> </div> </div> <divid="logArea"></div> </div> <script> // ============= 核心扫描控制 ============= let isRunning = false; let shouldStop = false; let abortController = null; let foundCount = 0; let scannedCount = 0; let errorCount = 0; let totalCount = 0; let startTime = 0; let timeoutId = null; const targetInput = document.getElementById('targetUrl'); const wordlistArea = document.getElementById('wordlist'); const concurrencySelect = document.getElementById('concurrency'); const methodSelect = document.getElementById('httpMethod'); const showErrorsCheck = document.getElementById('showErrors'); const showLogCheck = document.getElementById('showLog'); const progressBar = document.getElementById('progressBar'); const statusText = document.getElementById('statusText'); const statsText = document.getElementById('statsText'); const resultList = document.getElementById('resultList'); const logArea = document.getElementById('logArea'); const btnStart = document.getElementById('btnStart'); const btnStop = document.getElementById('btnStop'); // ============= 工具函数 ============= function log(msg, type = 'info') { if (!showLogCheck.checked) return; logArea.style.display = 'block'; const div = document.createElement('div'); div.className = `log-${type}`; div.textContent = `[${newDate().toLocaleTimeString()}] ${msg}`; logArea.appendChild(div); logArea.scrollTop = logArea.scrollHeight; } function toggleLog() { logArea.style.display = showLogCheck.checked ? 'block' : 'none'; } function statusClass(code) { if (typeof code === 'string' && code === 'opaque') return 'status-opaque'; if (code >= 200 && code < 300) return 'status-200'; if (code >= 300 && code < 400) return 'status-301'; if (code === 403 || code === 401) return 'status-403'; if (code >= 500) return 'status-500'; return ''; } function formatSize(bytes) { if (bytes === null || bytes === undefined || bytes === '?') return '?'; if (typeof bytes === 'string') return bytes; if (bytes === 0) return '0 B'; if (bytes < 1024) return bytes + ' B'; if (bytes < 1024*1024) return (bytes/1024).toFixed(1) + ' KB'; return (bytes/(1024*1024)).toFixed(1) + ' MB'; } function addResult(url, status, size, error = false) { const noResults = resultList.querySelector('.no-results'); if (noResults) noResults.remove(); const div = document.createElement('div'); div.className = 'result-item'; const urlSpan = document.createElement('span'); urlSpan.className = 'url'; urlSpan.textContent = url; const infoSpan = document.createElement('span'); infoSpan.style.display = 'flex'; infoSpan.style.alignItems = 'center'; infoSpan.style.gap = '6px'; if (error) { const badge = document.createElement('span'); badge.className = 'status status-error'; badge.textContent = '❌ 错误'; infoSpan.appendChild(badge); log(`连接失败: ${url}`, 'error'); } else if (status === 'opaque') { const badge = document.createElement('span'); badge.className = 'status status-opaque'; badge.textContent = '✅ 存在 (跨域)'; infoSpan.appendChild(badge); log(`发现(跨域): ${url}`, 'info'); } else { const codeSpan = document.createElement('span'); codeSpan.className = `status ${statusClass(status)}`; codeSpan.textContent = status; infoSpan.appendChild(codeSpan); const sizeSpan = document.createElement('span'); sizeSpan.className = 'size'; sizeSpan.textContent = ` / ${formatSize(size)}`; infoSpan.appendChild(sizeSpan); log(`发现: ${url} → [${status}] ${formatSize(size)}`, 'info'); } div.appendChild(urlSpan); div.appendChild(infoSpan); resultList.appendChild(div); resultList.scrollTop = resultList.scrollHeight; } function updateStats() { statsText.textContent = `已扫描: ${scannedCount} / ${totalCount} | 发现: ${foundCount} | 错误: ${errorCount}`; const pct = totalCount > 0 ? Math.round((scannedCount / totalCount) * 100) : 0; progressBar.style.width = pct + '%'; } function setStatus(msg) { statusText.textContent = msg; } // ============= 核心扫描逻辑(修复版) ============= async function scanSingle(url, method) { // 方案:先尝试正常模式获取状态码,如果CORS报错则改用 no-cors 模式 try { // 第一步:用普通模式请求(能获取状态码但可能跨域报错) const controller = new AbortController(); const timeoutTimer = setTimeout(() => controller.abort(), 8000); // 8秒超时 const resp = await fetch(url, { method: method, signal: controller.signal, headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': '*/*' }, redirect: 'follow', // 跟随重定向以获取最终状态码 mode: 'cors' // 尝试CORS模式 }); clearTimeout(timeoutTimer); // 如果能获取到响应,说明是同域或CORS允许 let size = 0; if (method === 'GET') { // 尝试读取部分内容 try { const clone = resp.clone(); const reader = clone.body.getReader(); let total = 0; while (total < 10240) { // 最多10KB const { done, value } = await reader.read(); if (done) break; total += value.length; } reader.cancel(); size = total; } catch (e) { size = '?'; } } else { // HEAD请求,从Content-Length获取大小 const cl = resp.headers.get('content-length'); size = cl ? parseInt(cl) : '?'; } return { status: resp.status, size: size, method: 'cors' }; } catch (err) { // CORS 错误或网络错误,改用 no-cors if (err.name === 'AbortError') { log(`超时: ${url}`, 'warn'); return null; // 超时不报告 } // 尝试 no-cors 模式 try { const controller2 = new AbortController(); const timeoutTimer2 = setTimeout(() => controller2.abort(), 8000); const resp2 = await fetch(url, { method: method, signal: controller2.signal, headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }, redirect: 'follow', mode: 'no-cors' // 不触发CORS错误 }); clearTimeout(timeoutTimer2); // no-cors 模式下 response 是 opaque,status为0 // 能请求成功说明路径存在 return { status: 'opaque', size: '?', method: 'no-cors' }; } catch (err2) { if (err2.name === 'AbortError') { log(`超时(no-cors): ${url}`, 'warn'); return null; } // 真正的网络错误(DNS失败、连接拒绝等) log(`连接错误: ${url} - ${err2.message}`, 'error'); return { status: 0, size: 0, error: true }; } } } async function startScan() { if (isRunning) return; const baseUrl = targetInput.value.trim().replace(/\/+$/, ''); if (!baseUrl || !baseUrl.startsWith('http')) { alert('请输入有效的URL(以 http:// 或 https:// 开头)'); return; } const paths = wordlistArea.value.split('\n') .map(p => p.trim()) .filter(p => p.length > 0); if (paths.length === 0) { alert('请输入至少一个扫描路径'); return; } const concurrency = parseInt(concurrencySelect.value); const method = methodSelect.value; const showErrors = showErrorsCheck.checked; // 重置状态 isRunning = true; shouldStop = false; foundCount = 0; scannedCount = 0; errorCount = 0; totalCount = paths.length; startTime = Date.now(); btnStart.disabled = true; btnStop.disabled = false; resultList.innerHTML = '<div class="no-results">扫描进行中...</div>'; if (showLogCheck.checked) { logArea.style.display = 'block'; logArea.innerHTML = ''; } progressBar.style.width = '0%'; setStatus('扫描中...'); updateStats(); log(`开始扫描: ${baseUrl} | 路径数: ${totalCount} | 并发: ${concurrency} | 方法: ${method}`, 'info'); // 并发扫描 - 使用信号量模式 const queue = [...paths]; let activeCount = 0; let completedCount = 0; async function worker() { while (queue.length > 0 && !shouldStop) { const path = queue.shift(); const url = baseUrl + '/' + path; scannedCount++; const result = await scanSingle(url, method); if (shouldStop) break; completedCount++; if (result === null) { // 超时,不计入结果 log(`超时跳过: ${url}`, 'warn'); } else if (result.error) { errorCount++; if (showErrors) { addResult(url, 0, 0, true); } } else if (result.status === 'opaque') { // no-cors 模式发现的可访问路径 foundCount++; addResult(url, 'opaque', '?'); } else if (result.status !== 404 && result.status !== 429 && result.status !== 0) { foundCount++; addResult(url, result.status, result.size); } updateStats(); // 动态状态更新 if (completedCount % 5 === 0 || completedCount === totalCount) { const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); setStatus(`扫描中... ${completedCount}/${totalCount} (${elapsed}s)`); } } activeCount--; if (activeCount === 0 && !shouldStop) { finishScan(); } } // 启动并发worker const workerCount = Math.min(concurrency, paths.length, 20); // 浏览器限制最多20 for (let i = 0; i < workerCount; i++) { activeCount++; worker(); } // 超时保护:如果长时间没有完成 if (timeoutId) clearTimeout(timeoutId); timeoutId = setTimeout(() => { if (isRunning) { log('扫描超时保护触发(60秒),强制停止', 'warn'); stopScan(); } }, 60000); } function finishScan() { if (!isRunning) return; isRunning = false; btnStart.disabled = false; btnStop.disabled = true; if (timeoutId) { clearTimeout(timeoutId); timeoutId = null; } const elapsed = ((Date.now() - startTime) / 1000).toFixed(2); setStatus(`✅ 扫描完成 (用时 ${elapsed}s)`); log(`扫描完成: 共扫描 ${scannedCount} 个路径, 发现 ${foundCount} 个结果`, 'info'); // 如果没有发现任何结果,显示提示 const noResults = resultList.querySelector('.no-results'); if (noResults) { if (foundCount === 0 && errorCount === 0) { noResults.textContent = `扫描完成 (${elapsed}s),未发现可访问的路径。`; } else if (foundCount === 0) { noResults.textContent = `扫描完成 (${elapsed}s),未发现可访问的路径。错误: ${errorCount}`; } } else if (foundCount === 0) { const div = document.createElement('div'); div.className = 'no-results'; div.textContent = `扫描完成 (${elapsed}s),未发现可访问的路径。`; resultList.appendChild(div); } updateStats(); } function stopScan() { if (!isRunning) return; shouldStop = true; if (timeoutId) { clearTimeout(timeoutId); timeoutId = null; } // 给正在进行的请求一点时间优雅结束 setTimeout(() => { setStatus('⏹ 已停止'); btnStart.disabled = false; btnStop.disabled = true; isRunning = false; const noResults = resultList.querySelector('.no-results'); if (noResults) { noResults.textContent = `扫描已停止 (已扫描 ${scannedCount}/${totalCount})`; } else { const div = document.createElement('div'); div.className = 'no-results'; div.textContent = `扫描已停止 (已扫描 ${scannedCount}/${totalCount})`; resultList.appendChild(div); } updateStats(); log(`扫描已停止: 已扫描 ${scannedCount}/${totalCount}`, 'warn'); }, 300); } function clearResults() { if (isRunning) { if (!confirm('扫描进行中,确定停止并清空?')) return; stopScan(); // 等待停止完成 setTimeout(() => { doClear(); }, 500); } else { doClear(); } } function doClear() { resultList.innerHTML = '<div class="no-results">等待扫描...</div>'; foundCount = 0; scannedCount = 0; errorCount = 0; totalCount = 0; progressBar.style.width = '0%'; setStatus('就绪'); updateStats(); logArea.innerHTML = ''; logArea.style.display = 'none'; } function exportResults() { const items = resultList.querySelectorAll('.result-item'); if (items.length === 0) { alert('没有可导出的结果'); return; } let text = '# 网站后台扫描结果\n'; text += `# 导出时间: ${newDate().toLocaleString()}\n`; text += `# 目标: ${targetInput.value.trim()}\n`; text += `# 请求方式: ${methodSelect.value}\n\n`; text += 'URL\t状态码\t大小\n'; items.forEach(item => { const url = item.querySelector('.url')?.textContent || ''; const statusElem = item.querySelector('.status'); const status = statusElem ? statusElem.textContent : ''; const sizeElem = item.querySelector('.size'); const size = sizeElem ? sizeElem.textContent.replace(' / ', '') : ''; text += `${url}\t${status}\t${size}\n`; }); const blob = new Blob([text], { type: 'text/plain;charset=utf-8' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `scan_result_${newDate().toISOString().slice(0,19).replace(/[:-]/g,'')}.txt`; a.click(); URL.revokeObjectURL(a.href); } function loadDefaultDict() { const defaultDict = `adminloginadmin/login.phpadmin/login.htmladmin/index.phpadmin/index.htmlmanagemanagermanagementbackenddashboardwp-adminadministratoradmin123rootsystemcontrolpanelcpanelcmsadmin/admin.phpadmin/manage.phpadmin/dashboard.phpuseruser/login.phpregistersignupapiapi/loginapi/v1api/v2testphpmyadminphpMyAdminmysqladminersetupconfiginstallbackupsqldatabaseuploaduploadsfilesfiledownload.git.git/config.env.svnrobots.txtsitemap.xmlcrossdomain.xmlindex.phpindex.htmldefault.aspxweb.config.htaccesshtaccess.txtconfig.phpconfig.inc.phpdb.phpdb.inc.phpconn.phpconn.inc.phpadmin/uploadadmin/upload.phpadmin/fileadmin/file.phpadmin/backupadmin/backup.phpadmin/databaseadmin/database.phpadmin/configadmin/config.phpadmin/useradmin/user.phpadmin/user_list.phpadmin/roleadmin/role.phpadmin/menuadmin/menu.phpadmin/settingadmin/setting.phpadmin/logout.php`; wordlistArea.value = defaultDict; } </script></body></html>