├── README.md ├── background.js ├── blank.html ├── contentScript.js ├── create_icons.js ├── images ├── ico16.png ├── icon128.png ├── icon16.png ├── icon48.png └── placeholder_icon.txt ├── instructions.html ├── manifest.json ├── popup.html └── popup.js /README.md: -------------------------------------------------------------------------------- 1 | # Synchro - 越权测试工具 2 | 3 | Synchro是一个Chrome浏览器扩展,用于测试网站的越权漏洞。它允许你在不同窗口登录不同账号,并将一个主窗口的所有操作同步到其他窗口,帮助发现权限控制缺陷。 4 | 5 | 6 | ![pic](https://github.com/user-attachments/assets/75378d19-71fe-4f94-a9f2-62b79cd717cd) 7 | 8 | 9 | ## 功能特点 10 | 11 | - 创建和管理多个浏览器窗口 12 | - 在不同窗口中登录不同账号 13 | - 将一个窗口(主窗口)的所有操作同步到其他窗口 14 | - 同步包括点击、表单输入、键盘操作和滚动等 15 | - 同步HTTP请求(尽可能) 16 | - 窗口状态指示器,显示当前窗口的同步状态 17 | 18 | ## 安装方法 19 | 20 | ### 方法1:从Chrome Web Store安装(待上架) 21 | 22 | 1. 访问Chrome Web Store(链接待添加) 23 | 2. 点击"添加到Chrome"按钮 24 | 25 | ### 方法2:开发者模式安装 26 | 27 | 1. 下载或克隆此仓库到本地 28 | 2. 打开Chrome浏览器,进入扩展管理页面 `chrome://extensions/` 29 | 3. 开启右上角的"开发者模式" 30 | 4. 点击"加载已解压的扩展程序"按钮 31 | 5. 选择本项目的文件夹 32 | 33 | ## 使用方法 34 | 35 | 1. 点击Chrome工具栏中的Synchro图标,打开控制面板 36 | 2. 点击"创建新窗口"按钮,输入要打开的URL(可选) 37 | 3. 在每个窗口中登录不同的账号 38 | 4. 在控制面板中,为每个窗口设置有意义的名称和账号标识 39 | 5. 选择一个窗口作为"主窗口"(所有操作将从此窗口同步到其他窗口) 40 | 6. 启用同步开关 41 | 7. 在主窗口中进行操作,观察其他窗口的行为 42 | ![image](https://github.com/user-attachments/assets/804b7f33-3f2a-42d2-bcff-f9600e9b5ac6) 43 | 44 | 45 | ## 测试场景示例 46 | 47 | ### 越权访问测试 48 | 49 | 1. 窗口A登录管理员账号 50 | 2. 窗口B登录普通用户账号 51 | 3. 在窗口A(主窗口)访问管理页面并执行操作 52 | 4. 观察窗口B是否也能执行相同操作,这可能表明存在越权漏洞 53 | 54 | ### 水平越权测试 55 | 56 | 1. 窗口A登录用户A账号 57 | 2. 窗口B登录用户B账号 58 | 3. 在窗口A(主窗口)访问用户A的个人资料或数据 59 | 4. 观察窗口B(用户B)是否能够看到或修改用户A的数据 60 | 61 | ## 注意事项 62 | 63 | - 本工具仅用于安全测试和教育目的 64 | - 在使用前请确保已获得测试网站的授权 65 | - 某些网站可能使用特殊技术(如token或复杂的会话管理)导致同步不完美 66 | - 扩展需要广泛的权限才能正常工作,请在可信环境中使用 67 | 68 | ## 隐私政策 69 | 70 | - 此扩展不收集或传输任何用户数据 71 | - 所有操作和数据都保留在您的浏览器内 72 | - 没有外部服务器或分析工具 73 | 74 | ## 贡献与反馈 75 | 76 | 欢迎提交问题和建议,或通过Pull Request贡献代码。 77 | 78 | ## Stargazers over time 79 | [![Stargazers over time](https://starchart.cc/rassec1/Privilege-Escalation-Testing-Assistant.svg?variant=adaptive)](https://starchart.cc/rassec1/Privilege-Escalation-Testing-Assistant) 80 | -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | // 全局变量 2 | let primaryWindowId = null; 3 | let secondaryWindows = []; 4 | let isCapturing = true; 5 | let isSyncEnabled = true; 6 | let capturedRequests = []; 7 | let originalResponses = new Map(); 8 | let pendingRequests = new Map(); // 用于跟踪待同步的请求 9 | 10 | // 调试开关 11 | const DEBUG = true; // 调试模式 12 | console.log('[越权测试助手] 后台脚本已加载'); 13 | 14 | // 添加更多的调试日志 15 | function logWindowDetails(window, context) { 16 | if (!window) { 17 | console.log(`[越权测试助手] [${context}] 窗口对象为空`); 18 | return; 19 | } 20 | 21 | console.log(`[越权测试助手] [${context}] 窗口详情: 22 | ID: ${window.id} 23 | 隐身模式: ${window.incognito} 24 | 类型: ${window.type} 25 | 状态: ${window.state} 26 | 尺寸: ${window.width}x${window.height} 27 | 位置: (${window.left}, ${window.top}) 28 | 标签页数: ${window.tabs ? window.tabs.length : '未知'} 29 | `); 30 | } 31 | 32 | // 监听扩展安装/更新 33 | chrome.runtime.onInstalled.addListener(() => { 34 | console.log('[越权测试助手] 扩展已安装/更新'); 35 | }); 36 | 37 | // 监听消息 38 | chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { 39 | console.log('[越权测试助手] 收到消息:', message); 40 | 41 | switch (message.action) { 42 | case 'createPrimaryWindow': 43 | createPrimaryWindow().then(sendResponse); 44 | break; 45 | 46 | case 'createSecondaryWindow': 47 | createSecondaryWindow(message.name).then(sendResponse); 48 | break; 49 | 50 | case 'closeWindow': 51 | closeWindow(message.windowId).then(sendResponse); 52 | break; 53 | 54 | case 'getState': 55 | getState().then(sendResponse); 56 | break; 57 | 58 | case 'setSyncEnabled': 59 | setSyncEnabled(message.enabled).then(sendResponse); 60 | break; 61 | 62 | case 'toggleCapturing': 63 | toggleCapturing(message.enabled).then(sendResponse); 64 | break; 65 | 66 | case 'clearRequests': 67 | clearRequests().then(sendResponse); 68 | break; 69 | 70 | case 'sendTestRequest': 71 | sendTestRequest(message.request).then(sendResponse); 72 | break; 73 | 74 | case 'linkClicked': 75 | case 'urlChanged': 76 | case 'pageLoaded': 77 | handleNavigation(message, sender).then(sendResponse); 78 | break; 79 | 80 | case 'requestCaptured': 81 | handleRequestCapture(message.request, sender).then(sendResponse); 82 | break; 83 | 84 | case 'requestCompleted': 85 | handleRequestComplete(message.request, sender).then(sendResponse); 86 | break; 87 | 88 | default: 89 | console.warn('[越权测试助手] 未知消息类型:', message.action); 90 | sendResponse({ success: false, error: '未知消息类型' }); 91 | } 92 | 93 | return true; // 保持消息通道开放 94 | }); 95 | 96 | // 创建普通窗口 97 | async function createPrimaryWindow() { 98 | try { 99 | const window = await chrome.windows.create({ 100 | url: 'https://www.baidu.com', 101 | type: 'normal', 102 | state: 'normal', 103 | width: 1200, 104 | height: 800 105 | }); 106 | 107 | if (!window || !window.id) { 108 | throw new Error('窗口创建失败'); 109 | } 110 | 111 | primaryWindowId = window.id; 112 | 113 | // 注入内容脚本 114 | await injectContentScript(window.id, 'normal', '普通窗口'); 115 | 116 | // 保存状态 117 | saveState(); 118 | 119 | return { success: true, window }; 120 | } catch (error) { 121 | console.error('[越权测试助手] 创建普通窗口失败:', error); 122 | return { success: false, error: error.message }; 123 | } 124 | } 125 | 126 | // 创建隐身窗口 127 | async function createSecondaryWindow(name = '隐身窗口') { 128 | try { 129 | const window = await chrome.windows.create({ 130 | url: 'about:blank', 131 | incognito: true, 132 | type: 'normal', 133 | width: 1200, 134 | height: 800, 135 | left: 100, 136 | top: 100 137 | }); 138 | 139 | if (!window || !window.id) { 140 | throw new Error('隐身窗口创建失败'); 141 | } 142 | 143 | // 等待窗口创建完成 144 | await new Promise(resolve => setTimeout(resolve, 500)); 145 | 146 | // 更新标签页URL 147 | const tabs = await chrome.tabs.query({ windowId: window.id }); 148 | if (!tabs || tabs.length === 0) { 149 | throw new Error('无法获取隐身窗口标签页'); 150 | } 151 | 152 | await chrome.tabs.update(tabs[0].id, { 153 | url: 'https://www.baidu.com' 154 | }); 155 | 156 | // 添加到窗口列表 157 | const secondaryWindow = { 158 | id: window.id, 159 | name: name || `隐身窗口 ${secondaryWindows.length + 1}`, 160 | createdAt: Date.now(), 161 | isIncognito: true 162 | }; 163 | 164 | secondaryWindows.push(secondaryWindow); 165 | 166 | // 注入内容脚本 167 | await injectContentScript(window.id, 'incognito', secondaryWindow.name); 168 | 169 | // 保存状态 170 | saveState(); 171 | 172 | return { success: true, window: secondaryWindow }; 173 | } catch (error) { 174 | console.error('[越权测试助手] 创建隐身窗口失败:', error); 175 | return { success: false, error: error.message }; 176 | } 177 | } 178 | 179 | // 注入内容脚本 180 | async function injectContentScript(windowId, windowType, windowName) { 181 | try { 182 | const tabs = await chrome.tabs.query({ windowId: windowId }); 183 | if (!tabs || tabs.length === 0) { 184 | throw new Error('未找到目标窗口的标签页'); 185 | } 186 | 187 | const tab = tabs[0]; 188 | 189 | // 注入内容脚本 190 | await chrome.scripting.executeScript({ 191 | target: { tabId: tab.id }, 192 | files: ['contentScript.js'] 193 | }); 194 | 195 | // 设置窗口类型 196 | await chrome.tabs.sendMessage(tab.id, { 197 | action: 'setWindowType', 198 | windowType: windowType, 199 | windowName: windowName 200 | }); 201 | 202 | return true; 203 | } catch (error) { 204 | console.error('[越权测试助手] 内容脚本注入失败:', error); 205 | throw error; 206 | } 207 | } 208 | 209 | // 关闭窗口 210 | async function closeWindow(windowId) { 211 | try { 212 | await chrome.windows.remove(windowId); 213 | 214 | // 更新窗口列表 215 | if (windowId === primaryWindowId) { 216 | primaryWindowId = null; 217 | } else { 218 | secondaryWindows = secondaryWindows.filter(w => w.id !== windowId); 219 | } 220 | 221 | // 保存状态 222 | saveState(); 223 | 224 | return { success: true }; 225 | } catch (error) { 226 | console.error('[越权测试助手] 关闭窗口失败:', error); 227 | return { success: false, error: error.message }; 228 | } 229 | } 230 | 231 | // 获取状态 232 | async function getState() { 233 | return { 234 | success: true, 235 | primaryWindowId, 236 | windows: [ 237 | ...(primaryWindowId ? [{ 238 | id: primaryWindowId, 239 | name: '普通窗口', 240 | isIncognito: false 241 | }] : []), 242 | ...secondaryWindows 243 | ], 244 | isCapturing, 245 | isSyncEnabled, 246 | capturedRequests 247 | }; 248 | } 249 | 250 | // 设置同步状态 251 | async function setSyncEnabled(enabled) { 252 | isSyncEnabled = enabled; 253 | saveState(); 254 | return { success: true }; 255 | } 256 | 257 | // 切换捕获状态 258 | async function toggleCapturing(enabled) { 259 | isCapturing = enabled; 260 | saveState(); 261 | return { success: true }; 262 | } 263 | 264 | // 清空请求 265 | async function clearRequests() { 266 | capturedRequests = []; 267 | originalResponses.clear(); 268 | saveState(); 269 | return { success: true }; 270 | } 271 | 272 | // 发送测试请求 273 | async function sendTestRequest(request) { 274 | try { 275 | // 获取原始响应 276 | const originalResponse = originalResponses.get(request.url); 277 | 278 | // 发送请求 279 | const response = await fetch(request.url, { 280 | method: request.method, 281 | headers: request.headers || {}, 282 | body: request.body, 283 | credentials: 'include' 284 | }); 285 | 286 | const responseText = await response.text(); 287 | let responseData; 288 | try { 289 | responseData = JSON.parse(responseText); 290 | } catch { 291 | responseData = responseText; 292 | } 293 | 294 | // 比较响应 295 | const comparisonResult = compareResponses(originalResponse, { 296 | status: response.status, 297 | body: responseData 298 | }); 299 | 300 | // 添加到捕获列表 301 | const capturedRequest = { 302 | ...request, 303 | timestamp: Date.now(), 304 | response: { 305 | status: response.status, 306 | body: responseData 307 | }, 308 | comparisonResult 309 | }; 310 | 311 | capturedRequests.unshift(capturedRequest); 312 | 313 | // 通知popup更新 314 | notifyPopup('updateRequests', { requests: capturedRequests }); 315 | 316 | return { success: true, response: capturedRequest }; 317 | } catch (error) { 318 | console.error('[越权测试助手] 发送测试请求失败:', error); 319 | return { success: false, error: error.message }; 320 | } 321 | } 322 | 323 | // 比较响应 324 | function compareResponses(original, current) { 325 | if (!original) return { isDifferent: null, details: '无原始响应用于比较' }; 326 | 327 | const differences = []; 328 | 329 | // 比较状态码 330 | if (original.status !== current.status) { 331 | differences.push(`状态码不同: ${original.status} -> ${current.status}`); 332 | } 333 | 334 | // 比较响应体 335 | if (typeof original.body === 'object' && typeof current.body === 'object') { 336 | const bodyDiffs = compareObjects(original.body, current.body); 337 | differences.push(...bodyDiffs); 338 | } else if (original.body !== current.body) { 339 | differences.push('响应内容不同'); 340 | } 341 | 342 | return { 343 | isDifferent: differences.length > 0, 344 | details: differences.join('\n') 345 | }; 346 | } 347 | 348 | // 比较对象 349 | function compareObjects(original, current, path = '') { 350 | const differences = []; 351 | 352 | // 获取所有键 353 | const allKeys = new Set([ 354 | ...Object.keys(original), 355 | ...Object.keys(current) 356 | ]); 357 | 358 | for (const key of allKeys) { 359 | const currentPath = path ? `${path}.${key}` : key; 360 | 361 | if (!(key in original)) { 362 | differences.push(`新增字段: ${currentPath}`); 363 | continue; 364 | } 365 | 366 | if (!(key in current)) { 367 | differences.push(`缺少字段: ${currentPath}`); 368 | continue; 369 | } 370 | 371 | if (typeof original[key] !== typeof current[key]) { 372 | differences.push(`类型不同: ${currentPath}`); 373 | continue; 374 | } 375 | 376 | if (typeof original[key] === 'object') { 377 | differences.push(...compareObjects(original[key], current[key], currentPath)); 378 | } else if (original[key] !== current[key]) { 379 | differences.push(`值不同: ${currentPath}`); 380 | } 381 | } 382 | 383 | return differences; 384 | } 385 | 386 | // 保存状态 387 | function saveState() { 388 | chrome.storage.local.set({ 389 | primaryWindowId, 390 | secondaryWindows, 391 | isCapturing, 392 | isSyncEnabled, 393 | capturedRequests: capturedRequests.slice(0, 100) // 只保存最近100条 394 | }); 395 | } 396 | 397 | // 加载状态 398 | async function loadState() { 399 | const state = await chrome.storage.local.get([ 400 | 'primaryWindowId', 401 | 'secondaryWindows', 402 | 'isCapturing', 403 | 'isSyncEnabled', 404 | 'capturedRequests' 405 | ]); 406 | 407 | primaryWindowId = state.primaryWindowId || null; 408 | secondaryWindows = state.secondaryWindows || []; 409 | isCapturing = state.isCapturing !== undefined ? state.isCapturing : true; 410 | isSyncEnabled = state.isSyncEnabled !== undefined ? state.isSyncEnabled : true; 411 | capturedRequests = state.capturedRequests || []; 412 | } 413 | 414 | // 通知popup 415 | function notifyPopup(action, data) { 416 | chrome.runtime.sendMessage({ 417 | action, 418 | ...data 419 | }); 420 | } 421 | 422 | // 监听请求 423 | chrome.webRequest.onBeforeRequest.addListener( 424 | details => { 425 | if (!isCapturing) return; 426 | 427 | // 记录原始请求 428 | const request = { 429 | url: details.url, 430 | method: details.method, 431 | body: details.requestBody, 432 | timestamp: Date.now() 433 | }; 434 | 435 | capturedRequests.unshift(request); 436 | 437 | // 通知popup 438 | notifyPopup('requestCaptured', { request }); 439 | }, 440 | { urls: [''] }, 441 | ['requestBody'] 442 | ); 443 | 444 | // 监听响应 445 | chrome.webRequest.onCompleted.addListener( 446 | details => { 447 | if (!isCapturing) return; 448 | 449 | // 存储原始响应 450 | originalResponses.set(details.url, { 451 | status: details.statusCode, 452 | body: details.responseBody 453 | }); 454 | }, 455 | { urls: [''] }, 456 | ['responseHeaders'] 457 | ); 458 | 459 | // 处理导航事件 460 | async function handleNavigation(message, sender) { 461 | try { 462 | // 只处理主窗口的导航事件 463 | if (!sender.tab || sender.tab.windowId !== primaryWindowId) { 464 | return { success: true }; 465 | } 466 | 467 | // 同步到所有隐身窗口 468 | for (const window of secondaryWindows) { 469 | try { 470 | const tabs = await chrome.tabs.query({ active: true, windowId: window.id }); 471 | if (!tabs || tabs.length === 0) continue; 472 | 473 | await chrome.tabs.sendMessage(tabs[0].id, { 474 | action: 'navigate', 475 | url: message.url, 476 | type: message.action 477 | }); 478 | } catch (error) { 479 | console.error(`[越权测试助手] 同步导航到窗口 ${window.id} 失败:`, error); 480 | } 481 | } 482 | 483 | return { success: true }; 484 | } catch (error) { 485 | console.error('[越权测试助手] 处理导航事件失败:', error); 486 | return { success: false, error: error.message }; 487 | } 488 | } 489 | 490 | // 处理请求捕获 491 | async function handleRequestCapture(request, sender) { 492 | try { 493 | // 只处理主窗口的请求 494 | if (!sender.tab || sender.tab.windowId !== primaryWindowId) { 495 | return { success: true }; 496 | } 497 | 498 | // 生成请求ID 499 | const requestId = generateRequestId(); 500 | request.id = requestId; 501 | 502 | // 存储请求信息 503 | pendingRequests.set(requestId, { 504 | request, 505 | timestamp: Date.now(), 506 | completed: false 507 | }); 508 | 509 | // 如果同步开启,发送到所有隐身窗口 510 | if (isSyncEnabled) { 511 | for (const window of secondaryWindows) { 512 | try { 513 | const tabs = await chrome.tabs.query({ active: true, windowId: window.id }); 514 | if (!tabs || tabs.length === 0) continue; 515 | 516 | await chrome.tabs.sendMessage(tabs[0].id, { 517 | action: 'replayRequest', 518 | request: request 519 | }); 520 | } catch (error) { 521 | console.error(`[越权测试助手] 同步请求到窗口 ${window.id} 失败:`, error); 522 | } 523 | } 524 | } 525 | 526 | return { success: true, requestId }; 527 | } catch (error) { 528 | console.error('[越权测试助手] 处理请求捕获失败:', error); 529 | return { success: false, error: error.message }; 530 | } 531 | } 532 | 533 | // 处理请求完成 534 | async function handleRequestComplete(request, sender) { 535 | try { 536 | const pendingRequest = pendingRequests.get(request.id); 537 | if (!pendingRequest) { 538 | return { success: true }; 539 | } 540 | 541 | // 更新请求状态 542 | pendingRequest.completed = true; 543 | pendingRequest.response = request.response; 544 | 545 | // 检查是否所有窗口都完成了请求 546 | const allCompleted = Array.from(pendingRequests.values()) 547 | .filter(r => r.request.id === request.id) 548 | .every(r => r.completed); 549 | 550 | if (allCompleted) { 551 | // 比较所有窗口的响应 552 | const responses = Array.from(pendingRequests.values()) 553 | .filter(r => r.request.id === request.id) 554 | .map(r => r.response); 555 | 556 | const comparisonResult = compareMultipleResponses(responses); 557 | 558 | // 添加到捕获列表 559 | const capturedRequest = { 560 | ...pendingRequest.request, 561 | timestamp: Date.now(), 562 | responses, 563 | comparisonResult 564 | }; 565 | 566 | capturedRequests.unshift(capturedRequest); 567 | 568 | // 通知popup更新 569 | notifyPopup('updateRequests', { requests: capturedRequests }); 570 | 571 | // 清理已完成的请求 572 | pendingRequests.delete(request.id); 573 | } 574 | 575 | return { success: true }; 576 | } catch (error) { 577 | console.error('[越权测试助手] 处理请求完成失败:', error); 578 | return { success: false, error: error.message }; 579 | } 580 | } 581 | 582 | // 比较多个响应 583 | function compareMultipleResponses(responses) { 584 | if (responses.length < 2) { 585 | return { isDifferent: false, details: '只有一个响应,无法比较' }; 586 | } 587 | 588 | const differences = []; 589 | const baseResponse = responses[0]; 590 | 591 | for (let i = 1; i < responses.length; i++) { 592 | const currentResponse = responses[i]; 593 | const windowDiffs = compareResponses(baseResponse, currentResponse); 594 | 595 | if (windowDiffs.isDifferent) { 596 | differences.push(`窗口 ${i} 与主窗口的差异:\n${windowDiffs.details}`); 597 | } 598 | } 599 | 600 | return { 601 | isDifferent: differences.length > 0, 602 | details: differences.join('\n\n') 603 | }; 604 | } 605 | 606 | // 生成请求ID 607 | function generateRequestId() { 608 | return Date.now().toString(36) + Math.random().toString(36).substring(2); 609 | } 610 | 611 | // 初始化 612 | loadState(); -------------------------------------------------------------------------------- /blank.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 越权测试助手 5 | 6 | 7 | 8 |
9 | 正在加载... 10 |
11 | 12 | -------------------------------------------------------------------------------- /contentScript.js: -------------------------------------------------------------------------------- 1 | // 存储状态 2 | let windowType = null; // 窗口类型: primary/secondary/isolated 3 | let windowName = null; // 窗口名称 4 | let isCapturing = true; // 是否正在捕获请求(默认开启) 5 | let isSyncEnabled = true; // 同步状态(默认开启) 6 | let capturedRequests = []; // 本地捕获的请求 7 | let debugMode = true; // 调试模式 8 | let isIsolated = false; // 是否是隔离会话 9 | let isPrimaryWindow = false; 10 | 11 | // 立即初始化 12 | console.log("[越权测试助手] 内容脚本已加载"); 13 | initialize(); 14 | 15 | // 页面加载完成后初始化UI 16 | document.addEventListener('DOMContentLoaded', () => { 17 | console.log("[越权测试助手] DOM已加载,初始化UI"); 18 | updateSyncIndicator(); 19 | 20 | if (debugMode) { 21 | addDebugInfo(); 22 | } 23 | }); 24 | 25 | // 初始化 26 | function initialize() { 27 | console.log("[越权测试助手] 初始化内容脚本..."); 28 | 29 | // 设置请求拦截 30 | interceptRequests(); 31 | 32 | // 请求当前窗口类型和状态 33 | chrome.runtime.sendMessage({ action: 'getState' }, response => { 34 | if (response) { 35 | console.log("[越权测试助手] 收到状态响应:", response); 36 | 37 | // 无法直接使用chrome.windows API,该API不在内容脚本的权限范围内 38 | // 通过tab.sender.tab.windowId或等待后台脚本主动设置windowType 39 | console.log("[越权测试助手] 主窗口ID:", response.primaryWindowId); 40 | console.log("[越权测试助手] 子窗口列表:", response.secondaryWindows); 41 | 42 | // 获取捕获状态和同步状态 43 | isCapturing = response.isCapturing !== undefined ? response.isCapturing : true; 44 | isSyncEnabled = response.isSyncEnabled !== undefined ? response.isSyncEnabled : true; 45 | 46 | console.log(`[越权测试助手] 捕获状态: ${isCapturing}, 同步状态: ${isSyncEnabled}`); 47 | 48 | // 如果DOM已加载,更新同步指示器 49 | if (document.body) { 50 | updateSyncIndicator(); 51 | } 52 | } else { 53 | console.log("[越权测试助手] 未收到状态响应"); 54 | } 55 | }); 56 | 57 | // 向后台发送准备就绪消息 58 | console.log("[越权测试助手] 发送内容脚本就绪消息"); 59 | 60 | // 由于内容脚本不能直接使用chrome.windows,发送就绪消息让后台脚本识别此窗口 61 | try { 62 | chrome.runtime.sendMessage({ 63 | action: 'contentScriptReady', 64 | windowId: -1 // 设置为-1,由后台脚本通过sender.tab.windowId自行识别 65 | }, response => { 66 | if (chrome.runtime.lastError) { 67 | console.error("[越权测试助手] 发送就绪消息失败:", chrome.runtime.lastError); 68 | } else { 69 | console.log("[越权测试助手] 就绪消息已发送,响应:", response); 70 | } 71 | }); 72 | } catch (error) { 73 | console.error("[越权测试助手] 发送就绪消息时发生异常:", error); 74 | } 75 | 76 | // 检查URL是否包含隔离会话标记 77 | if (window.location.search.includes('isolatedSession=true')) { 78 | console.log('[越权测试助手] 检测到隔离会话URL标记'); 79 | isIsolated = true; 80 | windowType = 'isolated'; 81 | windowName = '隔离会话窗口'; 82 | 83 | // 添加页面样式以指示这是隔离会话 84 | addIsolatedSessionIndicator(); 85 | } 86 | 87 | // 监听消息 88 | chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { 89 | console.log('[越权测试助手] 收到消息:', message); 90 | 91 | switch (message.action) { 92 | case 'setWindowType': 93 | windowType = message.windowType; 94 | windowName = message.windowName; 95 | isPrimaryWindow = windowType === 'normal'; 96 | console.log(`[越权测试助手] 窗口类型已设置: ${windowType}, 名称: ${windowName}`); 97 | break; 98 | 99 | case 'navigate': 100 | handleNavigation(message); 101 | break; 102 | 103 | case 'replayRequest': 104 | handleRequestReplay(message.request); 105 | break; 106 | 107 | case 'updateCapturingState': 108 | // 更新捕获状态 109 | isCapturing = message.isCapturing; 110 | console.log(`[越权测试助手] 更新捕获状态为 ${isCapturing ? '开启' : '关闭'}`); 111 | sendResponse({ success: true }); 112 | break; 113 | 114 | case 'updateSyncState': 115 | // 更新同步状态 116 | isSyncEnabled = message.isSyncEnabled; 117 | console.log(`[越权测试助手] 更新同步状态为 ${isSyncEnabled ? '开启' : '关闭'}`); 118 | updateSyncIndicator(); 119 | sendResponse({ success: true }); 120 | break; 121 | } 122 | 123 | return true; // 保持通道开放,允许异步响应 124 | }); 125 | } 126 | 127 | // 拦截页面请求 128 | function interceptRequests() { 129 | console.log("[越权测试助手] 设置请求拦截器"); 130 | 131 | try { 132 | // 保存原始方法 133 | const originalFetch = window.fetch; 134 | const originalXHR = window.XMLHttpRequest.prototype.open; 135 | const originalXHRSend = window.XMLHttpRequest.prototype.send; 136 | 137 | // 拦截fetch请求 138 | window.fetch = async function(resource, init = {}) { 139 | try { 140 | // 只在主窗口中捕获请求 141 | if (windowType === 'primary') { 142 | const url = (typeof resource === 'string') ? resource : resource.url; 143 | const method = init.method || 'GET'; 144 | 145 | console.log(`[越权测试助手] 捕获fetch请求: ${method} ${url}`); 146 | 147 | // 发送到后台脚本 148 | chrome.runtime.sendMessage({ 149 | action: 'capturedRequest', 150 | request: { 151 | id: generateRequestId(), 152 | url: url, 153 | method: method, 154 | timestamp: Date.now() 155 | } 156 | }, response => { 157 | console.log("[越权测试助手] 请求捕获响应:", response); 158 | }); 159 | } 160 | } catch (e) { 161 | console.error('[越权测试助手] 拦截fetch请求出错:', e); 162 | } 163 | 164 | // 调用原始方法 165 | return originalFetch.apply(this, arguments); 166 | }; 167 | 168 | // 拦截XMLHttpRequest.open 169 | window.XMLHttpRequest.prototype.open = function(method, url, async = true, user, password) { 170 | this._requestMethod = method; 171 | this._requestUrl = url; 172 | 173 | return originalXHR.apply(this, arguments); 174 | }; 175 | 176 | // 拦截XMLHttpRequest.send 177 | window.XMLHttpRequest.prototype.send = function(body) { 178 | try { 179 | // 只在主窗口中捕获请求 180 | if (windowType === 'primary' && this._requestUrl) { 181 | console.log(`[越权测试助手] 捕获XHR请求: ${this._requestMethod} ${this._requestUrl}`); 182 | 183 | // 发送到后台脚本 184 | chrome.runtime.sendMessage({ 185 | action: 'capturedRequest', 186 | request: { 187 | id: generateRequestId(), 188 | url: this._requestUrl, 189 | method: this._requestMethod, 190 | timestamp: Date.now() 191 | } 192 | }, response => { 193 | console.log("[越权测试助手] 请求捕获响应:", response); 194 | }); 195 | } 196 | } catch (e) { 197 | console.error('[越权测试助手] 拦截XHR请求出错:', e); 198 | } 199 | 200 | return originalXHRSend.apply(this, arguments); 201 | }; 202 | 203 | console.log('[越权测试助手] 请求拦截器已成功设置'); 204 | } catch (error) { 205 | console.error('[越权测试助手] 设置请求拦截器失败:', error); 206 | } 207 | } 208 | 209 | // 生成请求ID 210 | function generateRequestId() { 211 | return Date.now().toString(36) + Math.random().toString(36).substr(2, 5); 212 | } 213 | 214 | // 重放请求 215 | async function replayRequest(request) { 216 | console.log(`[越权测试助手] 重放请求: ${request.method} ${request.url}`); 217 | 218 | try { 219 | // 验证请求对象 220 | if (!request || !request.url) { 221 | throw new Error('无效的请求对象'); 222 | } 223 | 224 | // 验证请求URL 225 | let url = request.url; 226 | if (!url.startsWith('http')) { 227 | // 如果URL不是以http开头,可能是相对路径,尝试转换 228 | if (url.startsWith('/')) { 229 | url = window.location.origin + url; 230 | } else { 231 | url = window.location.origin + '/' + url; 232 | } 233 | console.log(`[越权测试助手] 转换后的URL: ${url}`); 234 | } 235 | 236 | // 使用fetch API重放请求,使用当前窗口的认证凭据 237 | const response = await fetch(url, { 238 | method: request.method || 'GET', 239 | credentials: 'include', // 包含当前窗口的cookies 240 | mode: 'no-cors', // 避免CORS问题 241 | cache: 'no-cache' 242 | }); 243 | 244 | console.log(`[越权测试助手] 重放请求成功,状态码: ${response.status}`); 245 | 246 | // 显示响应结果 247 | showResponseNotification(url, response.status); 248 | 249 | return { 250 | success: true, 251 | status: response.status 252 | }; 253 | } catch (error) { 254 | console.error('[越权测试助手] 重放请求失败:', error); 255 | 256 | // 确保DOM已加载,然后显示错误通知 257 | if (document.body) { 258 | showResponseNotification(request?.url || '未知URL', 'ERROR', error.message); 259 | } else { 260 | console.error('[越权测试助手] 无法显示错误通知,DOM未加载'); 261 | } 262 | 263 | return { 264 | success: false, 265 | error: error.message 266 | }; 267 | } 268 | } 269 | 270 | // 显示响应通知 271 | function showResponseNotification(url, status, errorMsg) { 272 | // 确保DOM已加载 273 | if (!document.body) { 274 | console.error('[越权测试助手] 无法显示通知,DOM未加载'); 275 | return; 276 | } 277 | 278 | // 创建通知元素 279 | const notification = document.createElement('div'); 280 | notification.style.cssText = ` 281 | position: fixed; 282 | bottom: 20px; 283 | right: 20px; 284 | padding: 10px 15px; 285 | background-color: ${status >= 200 && status < 400 ? '#4CAF50' : '#F44336'}; 286 | color: white; 287 | border-radius: 4px; 288 | box-shadow: 0 2px 5px rgba(0,0,0,0.2); 289 | font-family: Arial, sans-serif; 290 | font-size: 14px; 291 | z-index: 10000; 292 | max-width: 80%; 293 | `; 294 | 295 | // 截取URL 296 | const shortUrl = (url && url.length > 40) ? url.substring(0, 37) + '...' : (url || '未知URL'); 297 | 298 | notification.innerHTML = ` 299 |
${status} ${shortUrl}
300 |
301 | ${status >= 200 && status < 400 ? 302 | '✓ 请求重放成功' : 303 | '✗ 请求重放失败' + (errorMsg ? ': ' + errorMsg : '')} 304 |
305 | `; 306 | 307 | document.body.appendChild(notification); 308 | 309 | // 3秒后自动消失 310 | setTimeout(() => { 311 | if (notification.parentNode) { 312 | notification.parentNode.removeChild(notification); 313 | } 314 | }, 3000); 315 | } 316 | 317 | // 更新同步状态指示器 318 | function updateSyncIndicator() { 319 | // 如果DOM还没有加载完成,不执行 320 | if (!document.body) { 321 | console.log("[越权测试助手] DOM尚未加载完成,无法添加同步状态指示器"); 322 | return; 323 | } 324 | 325 | // 移除可能已存在的指示器 326 | const existingIndicator = document.getElementById('sync-status-indicator'); 327 | if (existingIndicator) { 328 | existingIndicator.remove(); 329 | } 330 | 331 | // 创建指示器元素 332 | const indicator = document.createElement('div'); 333 | indicator.id = 'sync-status-indicator'; 334 | indicator.style.cssText = ` 335 | position: fixed; 336 | top: 0; 337 | right: 0; 338 | padding: 5px 10px; 339 | background-color: ${isSyncEnabled ? '#4CAF50' : '#F44336'}; 340 | color: white; 341 | font-family: Arial, sans-serif; 342 | font-size: 12px; 343 | z-index: 10000; 344 | border-bottom-left-radius: 4px; 345 | `; 346 | 347 | // 设置指示器内容 348 | indicator.textContent = isSyncEnabled ? 349 | `✅ 同步已开启` : 350 | `❌ 同步已关闭`; 351 | 352 | // 添加到页面 353 | document.body.appendChild(indicator); 354 | 355 | // 添加点击事件 356 | indicator.addEventListener('click', toggleSync); 357 | } 358 | 359 | // 切换同步状态 360 | function toggleSync() { 361 | isSyncEnabled = !isSyncEnabled; 362 | 363 | chrome.runtime.sendMessage({ 364 | action: 'toggleSync', 365 | enabled: isSyncEnabled 366 | }, response => { 367 | if (response && response.success) { 368 | updateSyncIndicator(); 369 | } 370 | }); 371 | } 372 | 373 | // 添加调试信息面板 374 | function addDebugInfo() { 375 | // 如果已存在调试面板,则移除 376 | const existingPanel = document.getElementById('debug-panel'); 377 | if (existingPanel) { 378 | existingPanel.remove(); 379 | } 380 | 381 | // 创建调试面板 382 | const panel = document.createElement('div'); 383 | panel.id = 'debug-panel'; 384 | panel.style.cssText = ` 385 | position: fixed; 386 | bottom: 0; 387 | left: 0; 388 | width: 300px; 389 | background-color: rgba(0, 0, 0, 0.8); 390 | color: #fff; 391 | font-family: monospace; 392 | font-size: 12px; 393 | padding: 10px; 394 | z-index: 10000; 395 | max-height: 200px; 396 | overflow-y: auto; 397 | border-top-right-radius: 5px; 398 | `; 399 | 400 | // 添加调试信息 401 | panel.innerHTML = ` 402 |
窗口类型: ${windowType}
403 |
窗口名称: ${windowName}
404 |
捕获状态: ${isCapturing ? '开启' : '关闭'}
405 |
同步状态: ${isSyncEnabled ? '开启' : '关闭'}
406 |
已捕获请求: ${capturedRequests.length}
407 | 408 | 409 | `; 410 | 411 | // 添加到页面 412 | document.body.appendChild(panel); 413 | 414 | // 添加关闭按钮事件 415 | document.getElementById('toggle-debug').addEventListener('click', () => { 416 | debugMode = false; 417 | panel.remove(); 418 | }); 419 | 420 | // 添加测试同步按钮事件 421 | document.getElementById('test-sync').addEventListener('click', () => { 422 | // 只有在主窗口中才发送测试请求 423 | if (windowType === 'primary') { 424 | console.log('[越权测试助手] 发送测试同步请求'); 425 | 426 | // 发送到后台脚本 427 | chrome.runtime.sendMessage({ 428 | action: 'capturedRequest', 429 | request: { 430 | id: generateRequestId(), 431 | url: window.location.href + '?test=sync', 432 | method: 'GET', 433 | timestamp: Date.now() 434 | } 435 | }); 436 | } else { 437 | console.log('[越权测试助手] 子窗口不能发送测试请求'); 438 | alert('测试同步功能只能在主窗口中使用'); 439 | } 440 | }); 441 | } 442 | 443 | // 添加隔离会话指示器 444 | function addIsolatedSessionIndicator() { 445 | console.log('[越权测试助手] 添加隔离会话指示器'); 446 | 447 | // 添加样式到页面,微妙地标记这是隔离会话 448 | const style = document.createElement('style'); 449 | style.textContent = ` 450 | html { 451 | border: 2px solid #4CAF50; 452 | box-sizing: border-box; 453 | } 454 | 455 | #isolated-session-info { 456 | position: fixed; 457 | bottom: 10px; 458 | right: 10px; 459 | background-color: rgba(76, 175, 80, 0.8); 460 | color: white; 461 | padding: 5px 10px; 462 | border-radius: 5px; 463 | font-size: 12px; 464 | z-index: 9999; 465 | } 466 | `; 467 | document.head.appendChild(style); 468 | 469 | // 添加悬浮提示,表明这是隔离会话 470 | const infoElement = document.createElement('div'); 471 | infoElement.id = 'isolated-session-info'; 472 | infoElement.textContent = '隔离会话已启用 (ID: ' + (window._isolatedSessionId || 'unknown') + ')'; 473 | document.body.appendChild(infoElement); 474 | 475 | // 5秒后淡出提示 476 | setTimeout(() => { 477 | if (infoElement && infoElement.parentNode) { 478 | infoElement.style.opacity = '0.5'; 479 | } 480 | }, 5000); 481 | } 482 | 483 | // 执行请求 484 | async function executeRequest(request) { 485 | console.log('[越权测试助手] 执行请求:', request); 486 | 487 | // 目前简单实现,仅支持GET请求 488 | try { 489 | const response = await fetch(request.url, { 490 | method: request.method, 491 | headers: request.headers || {}, 492 | // 如果有body且是POST/PUT,添加body 493 | ...(request.body && ['POST', 'PUT'].includes(request.method.toUpperCase()) ? { body: request.body } : {}) 494 | }); 495 | 496 | const responseData = { 497 | status: response.status, 498 | statusText: response.statusText, 499 | headers: Object.fromEntries([...response.headers.entries()]), 500 | timestamp: Date.now() 501 | }; 502 | 503 | try { 504 | // 尝试解析为JSON 505 | responseData.body = await response.json(); 506 | responseData.bodyType = 'json'; 507 | } catch (e) { 508 | // 不是JSON,获取文本 509 | responseData.body = await response.text(); 510 | responseData.bodyType = 'text'; 511 | } 512 | 513 | return responseData; 514 | } catch (error) { 515 | console.error('[越权测试助手] 执行请求失败:', error); 516 | throw error; 517 | } 518 | } 519 | 520 | // 监听页面加载完成 521 | window.addEventListener('load', () => { 522 | console.log('[越权测试助手] 页面加载完成'); 523 | notifyBackgroundScript('pageLoaded', { url: window.location.href }); 524 | }); 525 | 526 | // 监听点击事件 527 | document.addEventListener('click', event => { 528 | // 查找被点击的链接 529 | const link = event.target.closest('a'); 530 | if (link && link.href) { 531 | console.log('[越权测试助手] 链接被点击:', link.href); 532 | notifyBackgroundScript('linkClicked', { url: link.href }); 533 | } 534 | }); 535 | 536 | // 监听表单提交 537 | document.addEventListener('submit', event => { 538 | const form = event.target; 539 | console.log('[越权测试助手] 表单提交:', form.action); 540 | notifyBackgroundScript('formSubmitted', { 541 | url: form.action, 542 | method: form.method 543 | }); 544 | }); 545 | 546 | // 监听 URL 变化 547 | let lastUrl = window.location.href; 548 | new MutationObserver(() => { 549 | if (window.location.href !== lastUrl) { 550 | console.log('[越权测试助手] URL 变化:', window.location.href); 551 | notifyBackgroundScript('urlChanged', { url: window.location.href }); 552 | lastUrl = window.location.href; 553 | } 554 | }).observe(document, { subtree: true, childList: true }); 555 | 556 | // 监听历史记录变化 557 | window.addEventListener('popstate', () => { 558 | console.log('[越权测试助手] 历史记录变化:', window.location.href); 559 | notifyBackgroundScript('urlChanged', { url: window.location.href }); 560 | }); 561 | 562 | // 通知后台脚本 563 | function notifyBackgroundScript(action, data) { 564 | chrome.runtime.sendMessage({ 565 | action: action, 566 | windowType: windowType, 567 | windowName: windowName, 568 | ...data 569 | }).catch(err => console.error('[越权测试助手] 发送消息失败:', err)); 570 | } 571 | 572 | // 处理导航 573 | async function handleNavigation(message) { 574 | try { 575 | const url = message.url; 576 | console.log(`[越权测试助手] 处理导航: ${url}`); 577 | 578 | // 根据导航类型执行不同的操作 579 | switch (message.type) { 580 | case 'linkClicked': 581 | // 模拟点击链接 582 | const link = document.querySelector(`a[href="${url}"]`); 583 | if (link) { 584 | link.click(); 585 | } else { 586 | window.location.href = url; 587 | } 588 | break; 589 | 590 | case 'urlChanged': 591 | case 'pageLoaded': 592 | window.location.href = url; 593 | break; 594 | } 595 | } catch (error) { 596 | console.error('[越权测试助手] 导航处理失败:', error); 597 | } 598 | } 599 | 600 | // 处理请求重放 601 | async function handleRequestReplay(request) { 602 | try { 603 | console.log(`[越权测试助手] 重放请求: ${request.method} ${request.url}`); 604 | 605 | // 准备请求选项 606 | const options = { 607 | method: request.method, 608 | headers: request.headers || {}, 609 | credentials: 'include' 610 | }; 611 | 612 | // 处理请求体 613 | if (request.body) { 614 | if (typeof request.body === 'string') { 615 | options.body = request.body; 616 | } else if (request.body.raw) { 617 | // 处理二进制数据 618 | const data = new Uint8Array(request.body.raw[0].bytes); 619 | options.body = data; 620 | } else if (request.body.formData) { 621 | // 处理表单数据 622 | const formData = new FormData(); 623 | for (const [key, value] of Object.entries(request.body.formData)) { 624 | formData.append(key, value); 625 | } 626 | options.body = formData; 627 | } 628 | } 629 | 630 | // 发送请求 631 | const response = await fetch(request.url, options); 632 | const responseText = await response.text(); 633 | 634 | let responseData; 635 | try { 636 | responseData = JSON.parse(responseText); 637 | } catch { 638 | responseData = responseText; 639 | } 640 | 641 | // 发送响应到background 642 | chrome.runtime.sendMessage({ 643 | action: 'requestCompleted', 644 | request: { 645 | id: request.id, 646 | response: { 647 | status: response.status, 648 | body: responseData 649 | } 650 | } 651 | }); 652 | 653 | return true; 654 | } catch (error) { 655 | console.error('[越权测试助手] 请求重放失败:', error); 656 | return false; 657 | } 658 | } -------------------------------------------------------------------------------- /create_icons.js: -------------------------------------------------------------------------------- 1 | // 创建扩展程序所需的图标文件 2 | // 使用Node.js fs模块复制文件 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | // 图标文件映射关系 7 | const iconMappings = [ 8 | { source: 'ico16.png', target: 'icon16.png' }, 9 | { source: 'icon48.png', target: 'icon32.png' }, // 复制48px图标并缩小作为32px 10 | { source: 'icon48.png', target: 'icon16_off.png' }, // 暂时用相同图标 11 | { source: 'icon48.png', target: 'icon32_off.png' }, // 暂时用相同图标 12 | { source: 'icon48.png', target: 'icon48_off.png' }, // 暂时用相同图标 13 | ]; 14 | 15 | // 图标目录 16 | const iconDir = path.join(__dirname, 'images'); 17 | 18 | console.log('开始创建图标文件...'); 19 | 20 | // 确保图标目录存在 21 | if (!fs.existsSync(iconDir)) { 22 | fs.mkdirSync(iconDir, { recursive: true }); 23 | console.log('创建图标目录:', iconDir); 24 | } 25 | 26 | // 复制图标文件 27 | iconMappings.forEach(mapping => { 28 | const sourcePath = path.join(iconDir, mapping.source); 29 | const targetPath = path.join(iconDir, mapping.target); 30 | 31 | try { 32 | if (fs.existsSync(sourcePath)) { 33 | fs.copyFileSync(sourcePath, targetPath); 34 | console.log(`✅ 成功复制: ${mapping.source} -> ${mapping.target}`); 35 | } else { 36 | console.error(`❌ 源文件不存在: ${mapping.source}`); 37 | } 38 | } catch (error) { 39 | console.error(`❌ 复制失败: ${mapping.source} -> ${mapping.target}`, error.message); 40 | } 41 | }); 42 | 43 | console.log('图标文件创建完成!'); -------------------------------------------------------------------------------- /images/ico16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rassec1/Privilege-Escalation-Testing-Assistant/83c510a1ef2d4a0a46129663985468b924008413/images/ico16.png -------------------------------------------------------------------------------- /images/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rassec1/Privilege-Escalation-Testing-Assistant/83c510a1ef2d4a0a46129663985468b924008413/images/icon128.png -------------------------------------------------------------------------------- /images/icon16.png: -------------------------------------------------------------------------------- 1 | �PNG 2 | 3 | 4 | IHDR ��7� sRGB ��� gAMA ���a pHYs � ��o�d lIDAT(S]�!�@Dq1��$�l�h� �I��d������&=2��ܮ��Mf�k���v\?iUf�w:���XPAm���x.�tA����xۭ4�m����b��A��e�" IEND�B`� -------------------------------------------------------------------------------- /images/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rassec1/Privilege-Escalation-Testing-Assistant/83c510a1ef2d4a0a46129663985468b924008413/images/icon48.png -------------------------------------------------------------------------------- /images/placeholder_icon.txt: -------------------------------------------------------------------------------- 1 | 请在此目录中创建以下图标文件: 2 | 3 | 1. icon16.png - 16x16像素的图标 4 | 2. icon48.png - 48x48像素的图标 5 | 3. icon128.png - 128x128像素的图标 6 | 7 | 您可以使用任何图像编辑软件创建这些图标,或者从网上下载适当的图标。 8 | 9 | 图标应该以某种方式表示"同步"或"窗口同步"的概念,例如可以使用箭头或窗口的符号。 10 | 11 | 这些图标文件在Chrome扩展清单(manifest.json)中被引用,缺少这些文件可能会导致扩展安装时出现警告。 -------------------------------------------------------------------------------- /instructions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 越权测试工具 - 使用说明 6 | 77 | 78 | 79 |
80 |

越权测试工具 - 使用说明

81 | 82 |

由于浏览器的安全限制,不同窗口之间的会话(Cookie/登录状态)无法完全隔离。为了进行有效的越权测试,请按照以下步骤操作:

83 | 84 |
85 | 重要提示: 使用多个浏览器配置文件(Profile)是测试越权漏洞的最佳方法,每个配置文件拥有完全独立的会话。 86 |
87 | 88 |
89 |

方法1:使用多个Chrome配置文件(推荐)

90 |
    91 |
  1. 点击右上角的配置文件图标,选择"添加"创建两个新的配置文件
  2. 92 |
  3. 分别命名为"管理员"和"普通用户"
  4. 93 |
  5. 在"管理员"配置文件中登录管理员账号
  6. 94 |
  7. 在"普通用户"配置文件中登录普通用户账号
  8. 95 |
  9. 在"管理员"窗口中进行操作,然后复制URL或请求到"普通用户"窗口测试
  10. 96 |
97 |
98 | 99 |
100 |

方法2:使用不同的浏览器

101 |
    102 |
  1. 使用Chrome登录管理员账号
  2. 103 |
  3. 使用Firefox或Edge登录普通用户账号
  4. 104 |
  5. 在Chrome中进行操作,然后将URL复制到Firefox/Edge中测试
  6. 105 |
106 |
107 | 108 |
109 |

方法3:使用隐私/无痕模式

110 |
    111 |
  1. 正常浏览器窗口登录管理员账号
  2. 112 |
  3. 无痕窗口(Ctrl+Shift+N)登录普通用户账号
  4. 113 |
  5. 在正常窗口操作,复制URL到无痕窗口测试
  6. 114 |
115 |
116 | 117 |
118 | 提示: 打开Chrome配置文件的方法:
119 | 1. 在地址栏输入 chrome://settings/manageProfile
120 | 2. 或者点击右上角的头像图标,选择"添加" 121 |
122 | 123 |
124 | 越权测试核心步骤: 125 |
    126 |
  1. 复制管理员操作的URL或API请求
  2. 127 |
  3. 在普通用户会话中重放该请求
  4. 128 |
  5. 如果请求成功,则证明存在越权漏洞
  6. 129 |
130 |
131 | 132 |

我们推荐配合使用浏览器开发者工具(F12)中的网络面板来捕获和分析请求。

133 | 134 | 135 |
136 | 137 | 142 | 143 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "越权测试助手", 3 | "version": "1.0", 4 | "description": "用于测试权限提升漏洞的Chrome扩展", 5 | "manifest_version": 3, 6 | "background": { 7 | "service_worker": "background.js", 8 | "type": "module" 9 | }, 10 | "action": { 11 | "default_title": "越权测试助手", 12 | "default_popup": "popup.html", 13 | "default_icon": { 14 | "16": "images/ico16.png", 15 | "48": "images/icon48.png", 16 | "128": "images/icon128.png" 17 | } 18 | }, 19 | "permissions": [ 20 | "tabs", 21 | "storage", 22 | "webRequest", 23 | "scripting", 24 | "webNavigation", 25 | "cookies", 26 | "activeTab", 27 | "webRequestAuthProvider" 28 | ], 29 | "host_permissions": [ 30 | "" 31 | ], 32 | "content_scripts": [ 33 | { 34 | "matches": [""], 35 | "js": ["contentScript.js"], 36 | "run_at": "document_start" 37 | } 38 | ], 39 | "incognito": "spanning", 40 | "icons": { 41 | "16": "images/ico16.png", 42 | "48": "images/icon48.png", 43 | "128": "images/icon128.png" 44 | }, 45 | "content_security_policy": { 46 | "extension_pages": "script-src 'self'; object-src 'self'", 47 | "sandbox": "sandbox allow-scripts; script-src 'self' 'unsafe-inline' 'unsafe-eval'; child-src 'self'" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 越权测试助手 6 | 514 | 515 | 516 |
517 | 518 |

越权测试助手

519 |
520 | 521 |
522 |
523 | 使用说明: 本工具支持以下越权测试场景:
524 | 1. URL参数越权:自动同步导航,测试URL参数修改
525 | 2. 表单提交越权:捕获表单提交,在隐身窗口中重放
526 | 3. API接口越权:捕获所有API请求,支持修改参数后重放
527 | 4. 水平越权:支持修改用户标识符进行测试
528 | 5. 垂直越权:支持修改角色/权限标识符测试 529 |
530 | 531 | 532 |
533 | 538 | 539 | 544 |
545 | 546 |
547 |
窗口管理
548 |
请求分析
549 |
越权测试
550 |
551 | 552 | 553 |
554 |
555 |
556 | 自动同步导航: 557 | 自动同步URL访问和表单提交 558 |
559 | 560 | 564 |
565 | 566 |
567 |
568 | 569 | 570 |
571 | 572 |
573 |

当前窗口

574 |
575 | 576 |
暂无窗口,请先创建窗口
577 |
578 |
579 |
580 |
581 | 582 | 583 |
584 |
585 | 586 | 587 | 594 |
595 | 596 |
597 |
598 |
尚未捕获任何请求
599 |
600 |
601 |
请选择一个请求查看详情
602 |
603 |
604 | 605 |
606 |
607 | 608 | 609 |
610 | 611 |
612 | 616 | 617 |
618 |
619 |
620 | 621 | 622 |
623 |
624 |
625 |

参数修改测试

626 |
627 | 628 | 629 | 630 |
631 |
632 | 633 | 634 | 635 |
636 |
637 | 638 |
639 |

API接口测试

640 |
641 | 642 | 648 |
649 |
650 | 651 |
652 | 653 |
654 | 655 |
656 |

测试结果

657 |
658 |
659 |
660 | GET /api/user/profile 661 | 可能存在越权 662 |
663 |
664 | 普通窗口返回完整数据,隐身窗口也返回相同数据 665 |
666 |
667 | 668 |
669 |
670 |
671 |
672 |
673 | 674 |
675 | 676 | 677 | 678 | -------------------------------------------------------------------------------- /popup.js: -------------------------------------------------------------------------------- 1 | // 全局变量 2 | let capturedRequests = []; // 捕获的请求列表 3 | let selectedRequest = null; // 当前选中的请求 4 | let isCapturing = true; // 捕获状态(默认开启) 5 | let primaryWindowId = null; // 主窗口ID 6 | let secondaryWindows = []; // 子窗口列表 7 | let isSyncEnabled = true; // 同步开关(默认开启) 8 | let originalResponses = {}; // 存储原始响应 9 | 10 | // DOM元素加载完成后初始化 11 | document.addEventListener('DOMContentLoaded', () => { 12 | console.log('[越权测试助手] 弹出页面已加载'); 13 | 14 | // 初始化标签页切换 15 | initTabs(); 16 | 17 | // 初始化窗口管理 18 | initWindowManagement(); 19 | 20 | // 初始化请求分析 21 | initRequestAnalysis(); 22 | 23 | // 初始化越权测试 24 | initPrivilegeTest(); 25 | 26 | // 获取初始状态 27 | updateState(); 28 | }); 29 | 30 | // 初始化标签页 31 | function initTabs() { 32 | const tabs = document.querySelectorAll('.tab'); 33 | const contents = document.querySelectorAll('.tab-content'); 34 | 35 | tabs.forEach(tab => { 36 | tab.addEventListener('click', () => { 37 | tabs.forEach(t => t.classList.remove('active')); 38 | contents.forEach(c => c.classList.remove('active')); 39 | 40 | tab.classList.add('active'); 41 | document.getElementById(tab.dataset.target).classList.add('active'); 42 | }); 43 | }); 44 | } 45 | 46 | // 初始化窗口管理 47 | function initWindowManagement() { 48 | // 快速操作按钮 49 | document.getElementById('quick-create-primary').addEventListener('click', createPrimaryWindow); 50 | document.getElementById('quick-create-incognito').addEventListener('click', createIncognitoWindow); 51 | 52 | // 常规按钮 53 | document.getElementById('create-primary').addEventListener('click', createPrimaryWindow); 54 | document.getElementById('create-secondary').addEventListener('click', createIncognitoWindow); 55 | 56 | // 同步开关 57 | document.getElementById('sync-toggle').addEventListener('change', event => { 58 | chrome.runtime.sendMessage({ 59 | action: 'setSyncEnabled', 60 | enabled: event.target.checked 61 | }); 62 | }); 63 | } 64 | 65 | // 初始化请求分析 66 | function initRequestAnalysis() { 67 | // 请求捕获控制 68 | document.getElementById('start-capture').addEventListener('click', toggleCapture); 69 | document.getElementById('clear-requests').addEventListener('click', clearRequests); 70 | 71 | // 请求过滤器 72 | document.getElementById('request-filter').addEventListener('change', filterRequests); 73 | 74 | // 请求操作按钮 75 | document.getElementById('copy-request').addEventListener('click', copyRequestAsCurl); 76 | document.getElementById('edit-request').addEventListener('click', showRequestEditor); 77 | document.getElementById('send-request').addEventListener('click', sendRequest); 78 | 79 | // 监听来自后台的请求捕获消息 80 | chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { 81 | if (message.action === 'requestCaptured') { 82 | addCapturedRequest(message.request); 83 | } 84 | }); 85 | } 86 | 87 | // 初始化越权测试 88 | function initPrivilegeTest() { 89 | // 水平越权测试 90 | document.querySelector('.test-section button:nth-child(1)').addEventListener('click', () => { 91 | const userId = document.getElementById('user-id').value; 92 | const userValue = document.getElementById('user-value').value; 93 | testHorizontalPrivilegeEscalation(userId, userValue); 94 | }); 95 | 96 | // 垂直越权测试 97 | document.querySelector('.test-section button:nth-child(2)').addEventListener('click', () => { 98 | const roleId = document.getElementById('role-id').value; 99 | const roleValue = document.getElementById('role-value').value; 100 | testVerticalPrivilegeEscalation(roleId, roleValue); 101 | }); 102 | 103 | // API测试 104 | document.querySelector('.test-section button.start').addEventListener('click', () => { 105 | const url = document.getElementById('api-url').value; 106 | const method = document.getElementById('http-method').value; 107 | const params = document.getElementById('request-params').value; 108 | testApiPrivilegeEscalation(url, method, params); 109 | }); 110 | } 111 | 112 | // 创建普通窗口 113 | async function createPrimaryWindow() { 114 | try { 115 | await chrome.runtime.sendMessage({ action: 'createPrimaryWindow' }); 116 | showNotification('普通窗口创建成功', 'success'); 117 | updateState(); 118 | } catch (error) { 119 | showNotification('普通窗口创建失败: ' + error.message, 'error'); 120 | } 121 | } 122 | 123 | // 创建隐身窗口 124 | async function createIncognitoWindow() { 125 | try { 126 | await chrome.runtime.sendMessage({ action: 'createSecondaryWindow' }); 127 | showNotification('隐身窗口创建成功', 'success'); 128 | updateState(); 129 | } catch (error) { 130 | showNotification('隐身窗口创建失败: ' + error.message, 'error'); 131 | } 132 | } 133 | 134 | // 更新窗口列表 135 | function updateWindowList(windows) { 136 | const windowList = document.getElementById('window-list'); 137 | 138 | if (!windows || windows.length === 0) { 139 | windowList.innerHTML = '
暂无窗口,请先创建窗口
'; 140 | return; 141 | } 142 | 143 | windowList.innerHTML = windows.map(window => ` 144 |
145 |
146 |
${window.name}
147 |
ID: ${window.id}
148 |
${window.isIncognito ? '隐身窗口' : '普通窗口'}
149 |
150 |
151 | 152 | 153 |
154 |
155 | `).join(''); 156 | } 157 | 158 | // 添加捕获的请求 159 | function addCapturedRequest(request) { 160 | capturedRequests.unshift(request); 161 | updateRequestList(); 162 | } 163 | 164 | // 更新请求列表 165 | function updateRequestList() { 166 | const requestList = document.getElementById('request-list'); 167 | const filter = document.getElementById('request-filter').value; 168 | 169 | const filteredRequests = filterRequestsByType(capturedRequests, filter); 170 | 171 | if (filteredRequests.length === 0) { 172 | requestList.innerHTML = '
尚未捕获任何请求
'; 173 | return; 174 | } 175 | 176 | requestList.innerHTML = filteredRequests.map((request, index) => ` 177 |
178 |
${request.method}
179 |
${request.url}
180 |
${new Date(request.timestamp).toLocaleTimeString()}
181 |
182 | `).join(''); 183 | } 184 | 185 | // 根据类型过滤请求 186 | function filterRequestsByType(requests, type) { 187 | if (type === 'all') return requests; 188 | 189 | return requests.filter(request => { 190 | switch (type) { 191 | case 'api': 192 | return request.url.includes('/api/'); 193 | case 'form': 194 | return request.method === 'POST' && request.headers['content-type']?.includes('form'); 195 | case 'get': 196 | return request.method === 'GET'; 197 | case 'post': 198 | return request.method === 'POST'; 199 | default: 200 | return true; 201 | } 202 | }); 203 | } 204 | 205 | // 测试水平越权 206 | async function testHorizontalPrivilegeEscalation(userId, userValue) { 207 | if (!selectedRequest) { 208 | showNotification('请先选择一个请求进行测试', 'warning'); 209 | return; 210 | } 211 | 212 | try { 213 | const modifiedRequest = modifyRequestParams(selectedRequest, { 214 | [userId]: userValue 215 | }); 216 | 217 | await sendTestRequest(modifiedRequest); 218 | showNotification('水平越权测试请求已发送', 'success'); 219 | } catch (error) { 220 | showNotification('水平越权测试失败: ' + error.message, 'error'); 221 | } 222 | } 223 | 224 | // 测试垂直越权 225 | async function testVerticalPrivilegeEscalation(roleId, roleValue) { 226 | if (!selectedRequest) { 227 | showNotification('请先选择一个请求进行测试', 'warning'); 228 | return; 229 | } 230 | 231 | try { 232 | const modifiedRequest = modifyRequestParams(selectedRequest, { 233 | [roleId]: roleValue 234 | }); 235 | 236 | await sendTestRequest(modifiedRequest); 237 | showNotification('垂直越权测试请求已发送', 'success'); 238 | } catch (error) { 239 | showNotification('垂直越权测试失败: ' + error.message, 'error'); 240 | } 241 | } 242 | 243 | // 测试API越权 244 | async function testApiPrivilegeEscalation(url, method, params) { 245 | try { 246 | const request = { 247 | url: url, 248 | method: method, 249 | body: params ? JSON.parse(params) : undefined 250 | }; 251 | 252 | await sendTestRequest(request); 253 | showNotification('API越权测试请求已发送', 'success'); 254 | } catch (error) { 255 | showNotification('API越权测试失败: ' + error.message, 'error'); 256 | } 257 | } 258 | 259 | // 修改请求参数 260 | function modifyRequestParams(request, params) { 261 | const modified = { ...request }; 262 | 263 | if (request.method === 'GET') { 264 | const url = new URL(request.url); 265 | Object.entries(params).forEach(([key, value]) => { 266 | url.searchParams.set(key, value); 267 | }); 268 | modified.url = url.toString(); 269 | } else { 270 | if (typeof request.body === 'string') { 271 | try { 272 | modified.body = JSON.parse(request.body); 273 | } catch { 274 | modified.body = request.body; 275 | } 276 | } 277 | 278 | if (typeof modified.body === 'object') { 279 | modified.body = { ...modified.body, ...params }; 280 | } 281 | } 282 | 283 | return modified; 284 | } 285 | 286 | // 发送测试请求 287 | async function sendTestRequest(request) { 288 | return chrome.runtime.sendMessage({ 289 | action: 'sendTestRequest', 290 | request: request 291 | }); 292 | } 293 | 294 | // 显示通知 295 | function showNotification(message, type = 'info') { 296 | const notifications = document.getElementById('notifications'); 297 | const notification = document.createElement('div'); 298 | notification.className = `notification ${type}`; 299 | notification.textContent = message; 300 | 301 | notifications.appendChild(notification); 302 | setTimeout(() => notification.remove(), 3000); 303 | } 304 | 305 | // 更新状态 306 | async function updateState() { 307 | const state = await chrome.runtime.sendMessage({ action: 'getState' }); 308 | 309 | // 更新同步开关 310 | document.getElementById('sync-toggle').checked = state.syncEnabled; 311 | 312 | // 更新窗口列表 313 | updateWindowList(state.windows); 314 | 315 | // 更新捕获状态 316 | document.getElementById('start-capture').textContent = 317 | state.isCapturing ? '停止捕获' : '开始捕获'; 318 | document.getElementById('start-capture').className = 319 | state.isCapturing ? 'action-btn stop' : 'action-btn start'; 320 | } 321 | 322 | // 切换同步状态 323 | function toggleSync(enabled) { 324 | console.log(`[越权测试助手] 切换同步状态: ${enabled}`); 325 | 326 | chrome.runtime.sendMessage({ 327 | action: 'toggleSync', 328 | enabled: enabled 329 | }, response => { 330 | if (response && response.success) { 331 | isSyncEnabled = enabled; 332 | showNotification(`已${enabled ? '开启' : '关闭'}自动同步请求`, 'info'); 333 | } 334 | }); 335 | } 336 | 337 | // 切换捕获状态 338 | function toggleCapture() { 339 | console.log(`[越权测试助手] 切换捕获状态: ${!isCapturing}`); 340 | 341 | chrome.runtime.sendMessage({ 342 | action: 'toggleCapturing', 343 | enabled: !isCapturing 344 | }, response => { 345 | if (response && response.success) { 346 | isCapturing = !isCapturing; 347 | updateCaptureButton(); 348 | showNotification(`已${isCapturing ? '开始' : '停止'}捕获请求`, 'info'); 349 | } 350 | }); 351 | } 352 | 353 | // 更新捕获按钮 354 | function updateCaptureButton() { 355 | console.log(`[越权测试助手] 更新捕获按钮: ${isCapturing}`); 356 | 357 | const captureButton = document.getElementById('start-capture'); 358 | if (!captureButton) return; 359 | 360 | if (isCapturing) { 361 | captureButton.textContent = '停止捕获'; 362 | captureButton.className = 'action-btn stop'; 363 | } else { 364 | captureButton.textContent = '开始捕获'; 365 | captureButton.className = 'action-btn start'; 366 | } 367 | } 368 | 369 | // 清空请求 370 | function clearRequests() { 371 | console.log('[越权测试助手] 清空请求'); 372 | 373 | chrome.runtime.sendMessage({ action: 'clearRequests' }, response => { 374 | if (response && response.success) { 375 | capturedRequests = []; 376 | selectedRequest = null; 377 | updateRequestList(); 378 | showNotification('已清空所有捕获的请求', 'info'); 379 | } 380 | }); 381 | } 382 | 383 | // 选择请求 384 | function selectRequest(index) { 385 | console.log('[越权测试助手] 选择请求:', capturedRequests[index]); 386 | 387 | selectedRequest = capturedRequests[index]; 388 | 389 | // 更新请求列表选中状态 390 | const requestItems = document.querySelectorAll('.request-item'); 391 | requestItems.forEach(item => { 392 | if (parseInt(item.dataset.index) === index) { 393 | item.classList.add('selected'); 394 | } else { 395 | item.classList.remove('selected'); 396 | } 397 | }); 398 | 399 | // 更新请求详情 400 | const requestDetails = document.getElementById('request-details'); 401 | if (!requestDetails) return; 402 | 403 | requestDetails.innerHTML = formatRequestDetails(selectedRequest); 404 | } 405 | 406 | // 格式化请求详情 407 | function formatRequestDetails(request) { 408 | console.log('[越权测试助手] 格式化请求详情:', request); 409 | 410 | if (!request) return '
请选择一个请求查看详情
'; 411 | 412 | // 格式化时间 413 | const time = new Date(request.timestamp).toLocaleString(); 414 | 415 | // 格式化cURL命令 416 | const curlCommand = formatAsCurl(request); 417 | 418 | // 格式化JSON 419 | const jsonData = JSON.stringify(request, null, 2); 420 | 421 | return ` 422 |
423 |
${request.method}
424 |
${request.url}
425 |
426 |
时间: ${time}
427 |
428 |
cURL 命令:
429 |
${curlCommand}
430 |
431 |
432 |
请求数据:
433 |
${jsonData}
434 |
435 | `; 436 | } 437 | 438 | // 格式化为cURL命令 439 | function formatAsCurl(request) { 440 | console.log('[越权测试助手] 格式化为cURL命令:', request); 441 | 442 | if (!request) return ''; 443 | 444 | let curl = `curl -X ${request.method} "${request.url}"`; 445 | 446 | // 添加头信息 447 | if (request.headers) { 448 | Object.entries(request.headers).forEach(([key, value]) => { 449 | curl += ` -H "${key}: ${value.replace(/"/g, '\\"')}"`; 450 | }); 451 | } 452 | 453 | // 添加body 454 | if (request.body && ['POST', 'PUT'].includes(request.method)) { 455 | curl += ` -d '${request.body}'`; 456 | } 457 | 458 | return curl; 459 | } 460 | 461 | // 复制请求详情 462 | function copyRequestAsCurl() { 463 | console.log('[越权测试助手] 复制请求详情'); 464 | 465 | if (!selectedRequest) { 466 | showNotification('请先选择一个请求', 'warning'); 467 | return; 468 | } 469 | 470 | // 复制cURL命令 471 | const curlCommand = formatAsCurl(selectedRequest); 472 | 473 | navigator.clipboard.writeText(curlCommand) 474 | .then(() => { 475 | showNotification('已复制为cURL命令', 'success'); 476 | }) 477 | .catch(err => { 478 | console.error('[越权测试助手] 复制失败:', err); 479 | showNotification('复制失败: ' + err.message, 'error'); 480 | }); 481 | } 482 | 483 | // 发送请求 484 | function sendRequest() { 485 | console.log('[越权测试助手] 发送请求'); 486 | 487 | if (!selectedRequest) { 488 | showNotification('请先选择一个请求', 'warning'); 489 | return; 490 | } 491 | 492 | // 获取目标 493 | const replayTarget = document.getElementById('replay-target'); 494 | const targetValue = replayTarget ? replayTarget.value : ''; 495 | 496 | if (targetValue === 'manual') { 497 | // 手动执行 498 | executeRequest(selectedRequest) 499 | .then(response => { 500 | console.log('[越权测试助手] 请求执行成功:', response); 501 | showNotification('请求已在当前窗口执行', 'success'); 502 | }) 503 | .catch(error => { 504 | console.error('[越权测试助手] 请求执行失败:', error); 505 | showNotification('请求执行失败: ' + error.message, 'error'); 506 | }); 507 | } else if (targetValue === '') { 508 | // 发送到所有子窗口 509 | if (secondaryWindows.length === 0) { 510 | showNotification('没有可用的子窗口,请先创建', 'warning'); 511 | return; 512 | } 513 | 514 | chrome.runtime.sendMessage({ 515 | action: 'replayRequest', 516 | request: selectedRequest, 517 | targetWindowId: null 518 | }, response => { 519 | if (response && response.success) { 520 | showNotification('请求已发送到所有子窗口', 'success'); 521 | } 522 | }); 523 | } else { 524 | // 发送到指定窗口 525 | const targetWindowId = parseInt(targetValue); 526 | 527 | chrome.runtime.sendMessage({ 528 | action: 'replayRequest', 529 | request: selectedRequest, 530 | targetWindowId: targetWindowId 531 | }, response => { 532 | if (response && response.success) { 533 | showNotification('请求已发送到指定窗口', 'success'); 534 | } 535 | }); 536 | } 537 | } 538 | 539 | // 执行请求 540 | async function executeRequest(request) { 541 | console.log('[越权测试助手] 执行请求:', request); 542 | 543 | try { 544 | const response = await fetch(request.url, { 545 | method: request.method, 546 | headers: request.headers || {}, 547 | // 如果有body且是POST/PUT,添加body 548 | ...(request.body && ['POST', 'PUT'].includes(request.method.toUpperCase()) ? { body: request.body } : {}) 549 | }); 550 | 551 | const responseText = await response.text(); 552 | let responseData; 553 | 554 | try { 555 | responseData = JSON.parse(responseText); 556 | } catch (e) { 557 | responseData = responseText; 558 | } 559 | 560 | return { 561 | status: response.status, 562 | statusText: response.statusText, 563 | headers: Object.fromEntries([...response.headers.entries()]), 564 | body: responseData, 565 | timestamp: Date.now() 566 | }; 567 | } catch (error) { 568 | console.error('[越权测试助手] 执行请求失败:', error); 569 | throw error; 570 | } 571 | } 572 | 573 | // 监听来自后台脚本的消息 574 | chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { 575 | console.log('[越权测试助手] 收到消息:', message); 576 | 577 | if (message.action === 'updateRequests') { 578 | // 更新请求列表 579 | capturedRequests = message.requests; 580 | updateRequestList(); 581 | sendResponse({ success: true }); 582 | } else if (message.action === 'showNotification') { 583 | // 显示通知 584 | showNotification(message.message, message.type); 585 | sendResponse({ success: true }); 586 | } 587 | 588 | return true; // 保持通道开放,允许异步响应 589 | }); --------------------------------------------------------------------------------