如何通过JS调用栈/错误栈Hook document.cookie并定位脚本?
我尝试通过JavaScript调用栈/错误栈机制Hook document.cookie的读写操作,以此获取对应的脚本及URL,但未能成功实现。使用Playwright作为自动化工具,以下是我的原始代码:
const { chromium } = require('playwright'); (async () => { const browser = await chromium.launch({headless: false}); const context = await browser.newContext(); const page = await context.newPage(); await page.goto('https://www.microsoft.com'); const callStackPromise = await page.evaluate(async () => { await new Promise(resolve => { window.onload = resolve; }); console.log('Evaluating call stack...'); const stack = new Error().stack.split('\n').slice(1).map(line => line.trim()); console.log('Call stack:', stack); let scripts = Array.from(document.scripts).map(script => script.src); console.log('Scripts:', scripts); let cf = []; for (let i = 0; i < stack.length; i++) { let line = stack[i]; let matches = line.match(/at (.*?) \(/); if (matches && matches[1].includes('document.cookie')) { let scriptUrl = scripts.find(scriptUrl => line.includes(scriptUrl)); if (scriptUrl) { cf.push({ url: scriptUrl }); } } } console.log('Call stack result:', cf); return cf; }); const callStack = await callStackPromise; console.log(callStack); await browser.close(); })();
问题分析
原始代码的核心问题在于:
- 仅在
window.onload后生成了当前执行上下文的错误栈,这个栈和document.cookie的读写操作完全无关,只是page.evaluate内部代码的调用栈,自然无法找到cookie相关的调用记录。 - 没有真正Hook
document.cookie的读写属性,无法在cookie被实际访问/修改时触发栈捕获逻辑。
解决方案
要实现需求,需要在页面加载前注入Hook逻辑,重写document.cookie的get和set方法,在每次读写操作时捕获调用栈并解析对应的脚本URL。
修改后的代码
const { chromium } = require('playwright'); (async () => { const browser = await chromium.launch({headless: false}); const context = await browser.newContext(); const page = await context.newPage(); // 在页面初始化阶段注入Hook,确保捕获所有Cookie操作 await page.addInitScript(() => { const cookieAccessRecords = []; // 保存原始的document.cookie属性描述符 const originalCookieDescriptor = Object.getOwnPropertyDescriptor(document, 'cookie'); // 重写document.cookie的getter和setter Object.defineProperty(document, 'cookie', { get() { // 读取Cookie时捕获调用栈 const stack = new Error().stack; const record = { type: 'read', scriptUrl: parseScriptUrlFromStack(stack), fullStack: stack }; cookieAccessRecords.push(record); // 调用原始getter返回结果 return originalCookieDescriptor.get.call(this); }, set(value) { // 设置Cookie时捕获调用栈 const stack = new Error().stack; const record = { type: 'write', cookieValue: value, scriptUrl: parseScriptUrlFromStack(stack), fullStack: stack }; cookieAccessRecords.push(record); // 调用原始setter设置Cookie originalCookieDescriptor.set.call(this, value); }, enumerable: true, configurable: true }); // 从错误栈中解析调用脚本的URL function parseScriptUrlFromStack(stack) { if (!stack) return null; // 跳过前两行栈帧(当前Hook的get/set方法本身) const stackLines = stack.split('\n').slice(2); for (const line of stackLines) { // 匹配Chrome风格的栈行URL(兼容带行号的格式) const urlMatch = line.match(/\((https?:\/\/[^:\)]+)/) || line.match(/at\s+(https?:\/\/\S+)/); if (urlMatch) { let url = urlMatch[1]; // 移除末尾的行号/列号信息 url = url.replace(/(:\d+)$/, ''); return url; } } return null; } // 将记录挂载到window,供后续获取 window.__cookieAccessLogs = cookieAccessRecords; }); await page.goto('https://www.microsoft.com'); // 页面加载完成后提取捕获的Cookie操作记录 const cookieLogs = await page.evaluate(() => { return window.__cookieAccessLogs; }); console.log('捕获的Cookie操作记录:'); cookieLogs.forEach(log => { console.log(`[${log.type}] 来源脚本: ${log.scriptUrl}`); if (log.cookieValue) console.log(` 设置的Cookie值: ${log.cookieValue}`); }); await browser.close(); })();
关键说明
- 提前注入Hook:使用
page.addInitScript在页面所有脚本执行前注入逻辑,避免错过页面加载早期的Cookie操作。 - 重写属性描述符:通过
Object.defineProperty重写document.cookie的读写方法,确保每次Cookie操作都会触发栈捕获。 - 栈解析逻辑:跳过Hook自身的栈帧,提取真实调用脚本的URL,处理不同浏览器的栈格式差异。
- 记录存储:将操作记录挂载到
window对象,方便后续通过page.evaluate获取并输出。
内容的提问来源于stack exchange,提问作者ZA09
相关产品推荐
相关产品推荐

