土薯工具 Toolshu.com 登錄 用戶注冊

如何檢測第三方 JavaScript 腳本對全局作用域的污染?——實戰監控 window 變量變化

原創 作者:bhnw 於 2025-11-10 15:49 編輯 24次瀏覽 收藏 (0)

在現代 Web 開發中,我們經常會引入第三方腳本——廣告、統計、SDK、插件等。這些腳本爲了“方便”,常常直接向全局作用域(window 對象)注入變量、函數甚至修改原生對象。這種行爲不僅會污染全局命名空間,還可能導致變量衝突、性能下降、安全風險,甚至悄無聲息地篡改關鍵 API(比如重寫 console.logXMLHttpRequest)。

那麼,作爲開發者,我們如何準確識別一個第三方腳本到底往 window 上加了什麼、改了什麼?本文將帶你手把手實現一個輕量級但可靠的監控方案。


一、問題場景

假設我們要加載一個廣告腳本:

<script src="https://cdn.wwads.cn/js/makemoney.js"></script>

我們想知道:

  • 它新增了哪些全局變量?
  • 它是否修改了已有的全局屬性(比如 ArrayPromise 或自定義變量)?

僅靠 Object.keys(window).length 只能知道數量變化,無法定位具體內容。我們需要更精細的對比。


二、核心思路:快照 + 差異對比

要檢測“變化”,最直接的方法是:

  1. 加載腳本前,對 window 做一次完整快照(屬性名 + 值)。
  2. 加載腳本後,再做一次快照。
  3. 對比兩次快照,找出新增項值發生變化的項

關鍵點:

  • 使用 Object.getOwnPropertyNames(window) 獲取所有屬性(包括不可枚舉的)。
  • 使用 Map 存儲屬性名與值的映射,便於高效查找。
  • 使用 Object.is() 進行值比較,避免 NaN-0/+0 等邊界問題。
  • 包裹 try...catch,防止訪問某些特殊屬性時拋出異常。

三、完整實現代碼

// 1. 創建 window 快照:記錄所有屬性及其當前值
function snapshotWindow() {
  const props = Object.getOwnPropertyNames(window);
  const snap = new Map();
  for (const key of props) {
    try {
      snap.set(key, window[key]);
    } catch (e) {
      // 某些屬性(如跨域 iframe 中的 opener)訪問會報錯
      snap.set(key, '<ACCESS_ERROR>');
    }
  }
  return snap;
}

// 2. 記錄加載前的狀態
const beforeSnapshot = snapshotWindow();

// 3. 動態加載第三方腳本
const script = document.createElement('script');
script.src = 'https://cdn.wwads.cn/js/makemoney.js';

// 4. 腳本加載完成後進行對比
script.onload = () => {
  // 稍作延遲,確保腳本完全執行(含微任務)
  setTimeout(() => {
    const afterSnapshot = snapshotWindow();

    const added = [];
    const modified = [];

    // 遍歷加載後的所有屬性
    for (const [key, newValue] of afterSnapshot) {
      if (!beforeSnapshot.has(key)) {
        added.push(key); // 新增
      } else {
        const oldValue = beforeSnapshot.get(key);
        if (!Object.is(oldValue, newValue)) {
          modified.push({ key, oldValue, newValue }); // 被修改
        }
      }
    }

    console.log('🟢 新增的全局變量:', added);
    console.log('🟡 被修改的全局變量:', modified);
  }, 100);
};

script.onerror = () => console.error('第三方腳本加載失敗');
document.head.appendChild(script);

四、輸出示例與解讀

運行上述代碼後,控制檯可能輸出:

🟢 新增的全局變量: ['_AdBlockInit', '_IsTrustedClick']
🟡 被修改的全局變量: [
  { key: "isMobile", oldValue: [Function], newValue: [Object] }
]

這意味着:

  • 腳本注入了 _AdBlockInit 等廣告相關變量;
  • 它可能重寫了 isMobile 對象

這類行爲在廣告或數據採集腳本中並不罕見,但對應用穩定性和安全性構成潛在威脅。


五、注意事項與進階建議

1. 動態屬性誤報

某些 window 屬性是 getter(如 window.devicePixelRatio),每次讀取值可能不同。這類屬性可能被誤判爲“修改”。可建立白名單過濾:

const DYNAMIC_PROPS = new Set(['devicePixelRatio', 'innerWidth', 'innerHeight', 'scrollY']);
if (DYNAMIC_PROPS.has(key)) return; // 跳過對比

2. 深度修改無法檢測

如果腳本只修改了某對象的內部屬性(如 window.myLib.config = {...}),而 myLib 引用未變,則不會被識別。如需深度監控,可結合 Proxy 或定期遍歷,但代價較高。

3. 生產環境慎用

此方案主要用於開發調試或安全審計,不建議在生產環境長期運行,因其會遍歷整個 window,有一定性能開銷。

4. 結合 CSP 與沙箱

對於高安全要求的場景,建議:

  • 使用 Content Security Policy (CSP) 限制腳本來源;
  • 將第三方腳本運行在 iframe 沙箱 中,徹底隔離全局作用域。

5. 隔離方案:運行後自動恢復關鍵全局變量

如果你已知某些第三方腳本會覆蓋特定全局變量(如 isMobileconfig),但又無法避免加載該腳本,可以採用“備份-恢復”策略,在腳本執行後自動還原關鍵變量,避免副作用擴散:

function loadThirdPartyScript(src, backupGlobals = []) {
  const backup = {};
  backupGlobals.forEach(key => {
    backup[key] = window[key];
  });

  const script = document.createElement('script');
  script.src = src;
  script.async = true;

  script.onload = script.onerror = () => {
    backupGlobals.forEach(key => {
      window[key] = backup[key];
    });
  };

  document.head.appendChild(script);
}

// 使用示例
loadThirdPartyScript('https://cdn.wwads.cn/js/makemoney.js ', ['isMobile', 'config']);

⚠️ 注意:此方法僅適用於腳本同步執行完畢後立即產生副作用的場景。若腳本通過 setTimeout、事件監聽器或異步回調持續修改全局變量,則恢復操作可能失效。


六、總結

第三方腳本如同“黑盒”,我們無法控制其實現,但可以主動監控其副作用。通過簡單的快照對比,就能清晰看到它對全局環境的“入侵”行爲,爲性能優化、安全審計和故障排查提供有力依據。

下次當你引入一個未知腳本時,不妨先問一句:“你到底往 window 上寫了什麼?”


💡 小技巧:將上述邏輯封裝爲一個函數 detectGlobalPollution(scriptUrl),可複用於任何第三方腳本的檢測。

发现周边 发现周边
評論區

加載中...

红包