You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过JS调用栈/错误栈Hook document.cookie并定位脚本?

如何Hook document.cookie并追踪读写它的脚本URL(Playwright环境)

我尝试通过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();
})();

问题分析

原始代码的核心问题在于:

  1. 仅在window.onload后生成了当前执行上下文的错误栈,这个栈和document.cookie的读写操作完全无关,只是page.evaluate内部代码的调用栈,自然无法找到cookie相关的调用记录。
  2. 没有真正Hookdocument.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 04:54:59