绕过 <head> 中的对象冻结(受到广泛保护的网站)

Piw*_*oli 5 javascript xss google-chrome csrf content-security-policy

背景

我遇到过一个带有广告横幅的网站。当广告拦截或类似内容删除横幅元素时,网站会重新发送alerts有关无法加载广告的垃圾邮件,从而使观看体验比广告更糟糕。

我喜欢修补并尝试打破网站上的这些“保护”,但是,此时我没有想法。

该网站似乎总体上采用了广泛的保护措施来防止篡改,因此,虽然这个问题“简单地”是关于在头部完成对象冻结的操作,但如何做到这一点存在多种限制。

问题

首先,我不想通过对横幅做一些特殊的事情来解决这个问题,例如使它们可见而不是删除它们。我想攻击alert.

问题是,当网站加载其 时index.html,它会冻结window.alert该部分中的对象,<head>如下所示:

<script>
    Object.defineProperty(window, 'alert', {
        configurable: false,
        writable: false
    })
</script>
Run Code Online (Sandbox Code Playgroud)

之后就不可能这样做了,例如:

window.alert = null;

鉴于我们无法触及警报本身,人们可能会考虑尝试查找并删除对警报的调用......

...不幸的是,这似乎也是不可能的,因为如果没有 JavaScript,网站将无法工作,并且网站使用CSRF-token加载其余脚本。所有加载的脚本还在每个请求中使用 CSRF 令牌。

该网站最初在头部加载一个“加载器脚本”,并且它受到原始策略的保护,因此无法从其他任何地方提供该脚本。该加载器脚本本身使用 CSRF 令牌通过 XMLHttpRequest 加载其余脚本。

还有内容安全策略标头。

这实质上意味着不可能例如让网络服务器充当代理来加载网页、剥离头部中的冻结脚本并传递修改后的网页。至少据我所知 - 据我所知,这正是 CSRF 令牌所阻止的。

我在哪里

我正在使用一个名为Resource Override的 Chrome 扩展,它可以让我:

  • 为网站设置基于正则表达式的规则和操作
  • 进一步设置基于正则表达式的 URL 规则和网站请求某些特定 URL 时的操作
  • “响应”网站发出的任何请求并响应,例如本地提供的文件,但限制是这个本地提供的文件显然会因需要 CSRF 令牌的所有内容而失败
  • 只要网站已加载,就可以在网站的任何位置注入 JavaScript
  • 修改请求和响应头

在头部注入js

对我来说,显而易见的第一步就是简单地将 a 注入window.alert = null<head>网站的 中,但这不起作用,因为当页面和页面的 html 加载时,对象冻结已经存在于头部中。

此外,这种方法永远行不通,因为只有当存在要注入脚本的页面时才能将脚本注入到页面中,但在这种情况下,当这样的页面存在时,对象冻结代码也已经存在。

解冻/解冻 window.alert 对象

Stackoverflow 上有许多关于在 JavaScript 中解冻/解冻冻结对象的问题和解答。总之,不可能。至少在某种程度上没有帮助。

原型污染

老实说,我对原型污染攻击的应用不太熟悉,但我确实对它们进行了一些研究,因为我觉得摆弄可以以__proto__某种方式让我使可window.alert配置或可写。看来这也不是办法。

我用我所掌握的知识尝试过的一件事是简单地扩展Object.prototype,它是所有对象的“根原型”,具有名为alert的属性,因此:

Object.prototype.alert = 999;
Run Code Online (Sandbox Code Playgroud)

它的作用是,当你现在做这样的事情时:

const arr = [1, 2, 3];
const str = "abc";

for(prop in arr) { 
    console.log(prop) // will log alert at some point
}

console.log(str.alert) // will log 999
Run Code Online (Sandbox Code Playgroud)

有趣的是,正确获取对象的所有可枚举属性名称(包括原型链(警报)中的属性名称)的唯一方法似乎是使用所示的循环for...in。所有其他方法,例如方便的命名方法Object.getOwnPropertyNames,只会直接返回对象的属性,而不返回其原型链。可以在此处找到相关文档。

无论如何,这个想法是:

A)幸运的话,也以某种方式覆盖 window.alert,因为 window 也是一个对象,因此应该由 Object.prototype.x 扩展
B)幸运的话,希望网站window.alert以某种可笑的方式调用,以便发出警报-所有东西的属性都会把事情搞砸

不出所料,这两件事都没有发生。

代理对象

我也玩过代理,最初的理解是它可以让我“挂钩”到函数调用中,这样我就可以拦截对警报函数本身的调用。但这不是代理的工作方式,所以吸取了教训。

不过,它确实提出了一个问题,是否可以通过其他方式拦截/监听 JavaScript 中的函数调用。显然,我可以用我自己的函数包装任何函数,但随后所有内容都需要调用包装函数而不是原始函数,这在这里没有帮助。

反映API

还有一个相当于“Reflection API”的 JavaScript,但从它的外观和我所做的一些测试来看,它也没有提供任何有关挂钩/监听/拦截/重定向函数调用的信息,尽管文档中指出那:

Reflect 是一个内置对象,提供可拦截 JavaScript 操作的方法。

使用 Chrome 进行本地覆盖

由 Chrome 本地覆盖修改的脚本不会通过某种完整性检查,因此它们将被阻止。

其他想法

像往常一样,我尝试了各种不同的想法,例如:

  • 尝试替换整个窗口对象,希望“解锁”window.alert
  • 天真地尝试再次将 window.alert 设置为可配置和可写
  • 尝试过Object.freeze = null,尽管在它可以设置为 null 或无论如何修改时它已经被调用了

怎么办

在我看来,一切皆有可能,尽管这种情况下的解决方案可能会很复杂。我想我已经用尽了大部分快速简单的方法,但我希望我错过了一些东西。

我仍在进行的一个想法是,首先尝试拦截和剥离内容安全策略标头,以更好地允许拦截和修改获取脚本的请求。我也不确定这最终是否会起作用,因为似乎还正在进行某种完整性验证。

有趣的是,似乎没有针对内联脚本的政策,所以这些政策确实有效。正因为如此,我对基于 JavaScript 的解决方案最感兴趣,但我会接受任何东西。

我也很感兴趣是否有一种方法可以在加载网站之前运行 JavaScript 或设置“修改的 JavaScript 上下文”?我想人们可以对 V8 进行修改并删除其核心的整个警报功能,但我正在寻找比这稍微少一点的东西。

回顾

  • 网站存在垃圾邮件window.alert
  • 网站冻结警报对象,使其不可修改
  • 网站需要 JavaScript 才能运行
  • 网站广泛使用 CSRF 令牌,甚至首先加载脚本,因此简单地交换文件是不可能的
  • 网站使用 XMLHttpRequests 加载所有重要脚本,并从 CSRF 令牌保护的脚本中执行此操作,该脚本本身受原始策略保护
  • 网站使用内容安全策略标头
  • 脚本文件具有适当的完整性检查,因此无法使用 chrome 本地覆盖修改
  • 目标是“简单地”禁止此类网站发出警报

最后的话

由于它的广泛性,我不会对此感到惊讶,但至少我写得很开心。我发现这真的很有趣,一个看似良好的网站及其特定功能(例如警报功能)现在可以防止篡改 - 调查这一点让我大开眼界。

即便如此,需要使用多少种不同的措施还是有点可笑的;来源检查、完整性检查、散列内联脚本、内容安全策略、CSRF 令牌、以非常特定的方式和在非常特定的位置加载的脚本……所有这些都是为了防止我在某种意义上将一个小警报功能设置为 null !

如果从这一切中得出一个结论,那就是一个巨大的问题:为什么不能简单地阻止网站发送警报?各种功能都有权限控制,为什么警报和提示不是其中之一呢?

编辑

就像我在自己的回答中所说的那样,事实证明该网站的防篡改措施比我预期的还要广泛,因此我在此添加了我事后挖掘的内容。即使设法使警报对象可写,它仍然不能那么容易地被替换,因为这会触发额外的显式防篡改检查。

在我的回答中,我将绕过所有这些检查,但这就是我们正在处理的内容:

tamperCheck()
{
    for (const check of [
        {ob: Function.prototype, fun: 'toString'},
        {ob: window, fun: 'alert'},
        {ob: window, fun: 'confirm'},
        {ob: Object, fun: 'freeze'},
        {ob: Object, fun: 'seal'},
        {ob: Object, fun: 'getPrototypeOf'},
        {ob: Object, fun: 'getOwnPropertyDescriptors'},
    ]) {
        const regex = new RegExp('^function ' + check.fun + '\\(\\)\\s*{\\s*\\[native code]\\s*}$');
        let d;

        try {
            d = delete check.ob[check.fun]['toString'];
        } catch {
            d = false;
        }

        if (
            !d
            || typeof Object.getPrototypeOf(check.ob[check.fun]) !== 'function'
            || typeof check.ob[check.fun] !== 'function'
            || check.ob[check.fun].name !== check.fun
            || check.ob[check.fun].toString().replaceAll('\n', '').match(regex) === null
        ) {
            return true;
        }

        Object.defineProperty(check.ob, check.fun, {value: check.ob[check.fun], configurable: false, writable: false});
    }

    return false;
}
Run Code Online (Sandbox Code Playgroud)

Piw*_*oli 2

解决方案

好吧,在打败这个东西之前我无法入睡,所以我就这么做了。这个解决方案结果非常复杂,希望有更容易和更简单的解决方案,但这是我的。

概述

此解决方案需要制作 chrome 扩展。具体来说,清单 V2 扩展。不幸的是,Manifest V2 扩展将在 2023 年不再受支持,正如我的扩展中的错误所示:

Manifest 版本 2 已弃用,并且将于 2023 年删除支持。有关更多详细信息,请参阅https://developer.chrome.com/blog/mv2-transition/ 。

但目前这有效。我对 chrome 扩展不太熟悉,所以这可能也可以通过 Manifest V3 扩展来完成。

该扩展可以做很多事情:

  • 它修改 main_frame 和 sub_frame 请求响应标头的 content-security-policy 标头,以便我们可以注入内联脚本
  • 它利用run_at: "document_start"内容脚本选项尽早执行脚本的某些部分(感谢@evolutionxbox)
  • 它使用MutationObserver<script>在执行之前拦截静态节点
  • 它将一个脚本注入到页面中,该脚本访问并修补window.alert
  • 它使用代理对象来隐藏window.alert防篡改检查的修补程序

绕过内容安全策略

当我切换到 Manifest V2 时,这个问题非常简单。我无法让它与 Manifest V3 扩展一起使用。我们使用declarativeNetRequest API 让扩展修改 content-security-policy 标头。

manifest.json 需要以下属性(除了通常的属性之外):

{
  "permissions": [
    "declarativeNetRequest",
    "declarativeNetRequestWithHostAccess",
    "declarativeNetRequestFeedback",
    "*://<website.domain>/*"
  ],

  "declarative_net_request" : {
    "rule_resources" : [
      {
        "id": "ruleset_1",
        "enabled": true,
        "path": "rules.json"
      }
    ]
  }
}
Run Code Online (Sandbox Code Playgroud)

rules.json 看起来像这样:

[
  {
    "id" : 1,
    "priority": 1,

    "action" : { 
      "type" : "modifyHeaders",

      "responseHeaders": [
        { "header": "content-security-policy", "operation": "set", "value": "default-src * data: blob: filesystem: about: ws: wss: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; " }
      ]
    },

    "condition" : {
      "urlFilter": "*://*/*",
      "resourceTypes": ["main_frame", "sub_frame", "script", "other"]
    }
  }
]
Run Code Online (Sandbox Code Playgroud)

这样的manifest.json与rules.json相结合使得扩展可以修改content-security-policy标头,以便可以将内联脚本注入到页面中。这里的文档解释了为什么我们显然必须使用 V2 清单版本。

run_at:“文档开始”

要在 document_start 处运行扩展,manifest.json 需要具有以下选项块:

"content_scripts": [
  {
    "matches": ["<website>"],
    "js": ["inject.js"],
    "run_at": "document_start"
  }
]
Run Code Online (Sandbox Code Playgroud)

这将使扩展在 document_start 处执行inject.js 中的任何内容,因此当文档尚未加载时。

拦截静态<script>节点创建

现在我们的脚本可以足够早地执行,因此在文档尚未加载之前,我们可以设置一个 MutationObserver 来拦截<script>网页中静态服务的节点添加。

这就是inject.js 的开头:

class ScriptInterceptor {
    #observer
    #config = { childList: true, subtree: true };
    #handlerFn

    constructor(handlerFn) {
        this.#handlerFn = handlerFn;
        this.#observer = new MutationObserver(this.#callback.bind(this));
        this.#observer.observe(document, this.#config);
    }

    #isScriptMutation = (node) => { 
        return node.tagName === "SCRIPT";
    }

    #callback(mutationList, observer) {
        for(const mutation of mutationList) {
            for(const node of mutation.addedNodes) {
                if(this.#isScriptMutation(node)) {
                    this.#handlerFn(node);
                }
            }
        }
    }
}

const scriptTrace = "Object.defineProperty(window,'alert',{configurable:false,writable:false})";

const interceptor = new ScriptInterceptor((script) => {
    if(script.textContent.includes(scriptTrace)) {
        script.textContent = "";
        script.remove();
    }
});
Run Code Online (Sandbox Code Playgroud)

那么这里发生了什么?我创建了一个 ScriptInterceptor 类,它在内部创建一个 MutationObserver 实例来监视整个文档中元素子列表或子树的任何更改。

当 MutationObserver 注意到一个或多个 DOM 突变时,我们会检查每个突变和受影响的 DOM 节点,同时过滤掉所有不是节点的内容<script>

当我们遇到<script>节点时,我们调用处理函数并将找到的节点传递给它。在这种情况下,我们的处理函数如下所示:

(script) => {
  if(script.textContent.includes(scriptTrace)) {
    script.textContent = "";
    script.remove();
  }
}
Run Code Online (Sandbox Code Playgroud)

其中是冻结对象的scriptTrace特定元素的代码内容:<script>window.alert

const scriptTrace = "Object.defineProperty(window,'alert',{configurable:false,writable:false})";
Run Code Online (Sandbox Code Playgroud)

这消除了对象的冻结,但我们仍然没有对其本身进行任何操作window.alert来禁用它。

<script>关于 MutationObserver 和静态服务块的旁注

所以我在这里经常使用静态这个术语,这是有原因的。此 MutationObserver 方法不适用于动态添加到页面的脚本,例如以下脚本:

const dynamicScript = document.createElement('script');
dynamicScript.text = `(() => { console.log("Hello!") })()`;
document.documentElement.appendChild(dynamicScript);
Run Code Online (Sandbox Code Playgroud)

虽然 MutationObserver 会捕获它们被添加到页面中,但它只会在脚本执行后才会这样做。

我的情况不需要处理这样的拦截脚本,但如果有人发现自己处于这种情况,我确实出于兴趣找到了一种解决方案:

Element.prototype.appendChild = () => { };
Element.prototype.append = () => { };
Element.prototype.prepend = () => { };
Element.prototype.after = () => { };
Element.prototype.before = () => { };
Element.prototype.insertAdjacentElement = () => { };
Element.prototype.insertAdjacentHTML = () => { };
Element.prototype.insertAdjacentText = () => { };
Element.prototype.replaceChildren = () => { };
Element.prototype.replaceWith = () => { };
Element.prototype.setHTML = () => { };

Object.defineProperty(Element.prototype, "appendChild", { configurable:false, writable:false });
Object.defineProperty(Element.prototype, "append", { configurable:false, writable:false });
Object.defineProperty(Element.prototype, "prepend", { configurable:false, writable:false });
Object.defineProperty(Element.prototype, "after", { configurable:false, writable:false });
Object.defineProperty(Element.prototype, "before", { configurable:false, writable:false });
Object.defineProperty(Element.prototype, "insertAdjacentElement", { configurable:false, writable:false });
Object.defineProperty(Element.prototype, "insertAdjacentHTML", { configurable:false, writable:false });
Object.defineProperty(Element.prototype, "insertAdjacentText", { configurable:false, writable:false });
Object.defineProperty(Element.prototype, "replaceChildren", { configurable:false, writable:false });
Object.defineProperty(Element.prototype, "replaceWith", { configurable:false, writable:false });
Object.defineProperty(Element.prototype, "setHTML", { configurable:false, writable:false });
Run Code Online (Sandbox Code Playgroud)

这只是一个样板,但这里的想法是,我们实际上可以覆盖所有可能用于将<script>标签插入页面的方法,并由它们自己锁定它们。重点不是将方法留空,而是在那里实现某种逻辑来捕获并取消感兴趣的动态脚本的插入,并使其他一切正常工作。偏离主题,但可能很高兴知道。

修补window.alert并使其无法检测到

ject.js 脚本的其余部分如下所示:

const inject = () => {
    const fn = () => {

        // Alert actor
        const alert = () => { }

        alert.toString = () => { return "function alert() { [native code] }" }

        const antiTamperCheckFaker = {
            deleteProperty() {
                return true;
            },

            getPrototypeOf() {
                return () => { };
            }
        }

        const alertProxy = new Proxy(alert, antiTamperCheckFaker);
        window.alert = alertProxy;
        Object.defineProperty(window, 'alert', { configurable: false, writable: false });
    }

    const e = document.createElement('script');
    e.text = `(${fn.toString()})()`;
    document.documentElement.appendChild(e);
}

inject();
Run Code Online (Sandbox Code Playgroud)

我们在这里所做的事情本质上很简单——我们正在变成window.alert一个不执行任何操作的函数() => { }

然而,当我到达这一点时,网站开始变砖,因为window.alert网页中的一些防篡改检查检测到了我的补丁。深入挖掘,我发现了以下篡改检查功能(我也将编辑原始问题):

tamperCheck()
{
    for (const check of [
        {ob: Function.prototype, fun: 'toString'},
        {ob: window, fun: 'alert'},
        {ob: window, fun: 'confirm'},
        {ob: Object, fun: 'freeze'},
        {ob: Object, fun: 'seal'},
        {ob: Object, fun: 'getPrototypeOf'},
        {ob: Object, fun: 'getOwnPropertyDescriptors'},
    ]) {
        const regex = new RegExp('^function ' + check.fun + '\\(\\)\\s*{\\s*\\[native code]\\s*}$');
        let d;

        try {
            d = delete check.ob[check.fun]['toString'];
        } catch {
            d = false;
        }

        if (
            !d
            || typeof Object.getPrototypeOf(check.ob[check.fun]) !== 'function'
            || typeof check.ob[check.fun] !== 'function'
            || check.ob[check.fun].name !== check.fun
            || check.ob[check.fun].toString().replaceAll('\n', '').match(regex) === null
        ) {
            return true;
        }

        Object.defineProperty(check.ob, check.fun, {value: check.ob[check.fun], configurable: false, writable: false});
    }

    return false;
}
Run Code Online (Sandbox Code Playgroud)

它看起来有点混乱,但如果我们这样说,我们可以让事情变得更简单:

check.ob = windowcheck.fun = alert

首先,我们可以将check.funRegExp 行中的 替换为“alert”:

const regex = new RegExp('^function alert \\(\\)\\s*{\\s*\\[native code]\\s*}$');
Run Code Online (Sandbox Code Playgroud)

目前该正则表达式尚未实际使用,但我会回过头来讨论它。接下来我们有 try...catch 块:

let d;

try {
    d = delete check.ob[check.fun]['toString'];
} catch {
    d = false;
}
Run Code Online (Sandbox Code Playgroud)

这本质上是检查是否可以delete window["alert"]["toString"]成功执行。如果我们在这里查看我的代码:

Object.defineProperty(window, 'alert', { configurable: false, writable: false });
Run Code Online (Sandbox Code Playgroud)

我自己冻结警报对象,因此此delete操作将失败,因为您无法toString从冻结对象中删除诸如 之类的属性或方法。

这是 Proxy 对象第一次发挥作用的地方。正如您可以从我的代码中读到的那样,我们实际上并没有设置window.alert = alert,而是正在执行window.alert = alertProxy。这个alertProxy基本上是const alert = () => { }我们之前定义的,但它还有一个额外的优点,我们可以为其指定某些处理程序:

const antiTamperCheckFaker = {
    deleteProperty() {
        return true;
    },

    getPrototypeOf() {
        return () => { };
    }
}
Run Code Online (Sandbox Code Playgroud)

为了绕过这个删除检查,我们有点“挂钩”删除函数,该函数在delete window["alert"]["toString"]执行时被调用。我们正在覆盖默认行为,以不删除任何内容,而是简单地说“一切都好,已删除!”。我们不能toString删除它,因为我们也覆盖了它,稍后我们需要它。

接下来是typeof Object.getPrototypeOf(check.ob[check.fun]) !== 'function'检查,我们可以将其写为:

typeof Object.getPrototypeOf(window["alert"]) !== 'function'
Run Code Online (Sandbox Code Playgroud)

这会检查 的原型是否window.alert是一个函数(*)。这不一定是问题,因为我们的“警报参与者”应该是一个函数,但我根据经验记得,在某些情况下,代理的使用可能会触发这样的检查。如果我们想要或必须为我们的警报参与者使用对象而不是函数,那么这个检查也会失败。如果需要的话,可以使用getPrototypeOf我们内部的处理程序来绕过这个问题。antiTamperCheckFaker

(*) 虽然它清楚地说 !== 'function',但如果任何检查为 true,则 if 子句作为整体返回 true,因此 ifwindow.alert不是函数,则检查的该部分将评估为 true,从而使检查返回true,这是不好的。

然后是check.ob[check.fun] !== 'function'检查,检查window.alert它本身是否是一个函数。非常不言自明,我们用来替换的警报window.alert只需是一个函数即可。

如果我们可以用一个对象替换的话,我们可以更容易地绕过这些检查window.alert,但是这个检查本质上是要求我们替换的任何东西都window.alert必须是某种函数。

然后我们有check.ob[check.fun].name !== check.fun检查。该函数检查当前函数的名称是否alert实际上window未命名为alert。如果我们有:

const myPatchedAlert = () => { }

代替:

const alert = () => { }

check.ob[check.fun].name在这种情况下,将评估为“myPatchedAlert” 。

然后我们有一张更大的支票:

check.ob[check.fun].toString().replaceAll('\n', '').match(regex) === null
Run Code Online (Sandbox Code Playgroud)

现在这个很有趣。如果您在开发控制台中输入内容,您可以亲眼看看会发生什么window.alert.toString(),但我会为您破坏它并说它将输出'function alert() { [native code] }'. 我们之前看到的丑陋的正则表达式,所以这个:

const regex = new RegExp('^function alert \\(\\)\\s*{\\s*\\[native code]\\s*}$');
Run Code Online (Sandbox Code Playgroud)

本质上只是进行比较,例如:

window.alert.toString() === 'function alert() { [native code] }'

当我们的警报const alert = () => { }.toString()调用时,它将输出() => { }导致此检查失败的信息。幸运的是,我们重写了toString()警报的方法以返回类似的[本机代码]:

alert.toString = () => { return "function alert() { [native code] }" }
Run Code Online (Sandbox Code Playgroud)

虽然创建我们自己的toString()方法并让它返回我们想要的任何内容很容易,但请记住,早期的篡改检查实际上是toString()在运行此检查之前尝试从警报中删除该方法。如果无法删除它,检查就会失败。

我们toString()通过冻结自己的警报来保护我们的方法不被删除,但我们还必须拦截删除操作,以便检查会认为它已成功删除了不可删除的toString().

顺便说一句,我们不能使用Function.prototype.toString = ...,这会保护它不被删除,因为它将是一个原型函数而不是alert 的成员,因为检查也恰好检查 of 是否toStringFunction.prototype被篡改。

事实上,事实并非如此。Bergi 给出了一个巧妙Function.prototype的解决方案:

Function.prototype.toString = function toString() { 
    return `function ${this.name}() { [native code] }`; 
};
Run Code Online (Sandbox Code Playgroud)

反正

我必须道歉。整件事有点失控了。SO 可能不是像我在这里那样进行自我实现冒险的最佳场所。

为了我的辩护,我确实为许多我想的关于扩展、脚本注入、防篡改方法和浏览器安全性的不寻常问题提出了不错的解决方案。希望也许有人发现自己在这里寻找答案。

完整文件

清单.json

{
  "name": "document_start script exector",
  "description": "document_start script exector",
  "version": "1.0",
  "manifest_version": 2,
  "permissions": [
    "declarativeNetRequest",
    "declarativeNetRequestWithHostAccess",
    "declarativeNetRequestFeedback",
    "*://<website>/*"
  ],

  "content_scripts": [
    {
      "matches": ["<website>"],
      "js": ["inject.js"],
      "run_at": "document_start"
    }
  ],

  "declarative_net_request" : {
    "rule_resources" : [
      {
        "id": "ruleset_1",
        "enabled": true,
        "path": "rules.json"
      }
    ]
  }
}
Run Code Online (Sandbox Code Playgroud)

规则.json

[
  {
    "id" : 1,
    "priority": 1,

    "action" : { 
      "type" : "modifyHeaders",

      "responseHeaders": [
        { "header": "content-security-policy", "operation": "set", "value": "default-src * data: blob: filesystem: about: ws: wss: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; " }
      ]
    },

    "condition" : {
      "urlFilter": "*://*/*",
      "resourceTypes": ["main_frame", "sub_frame", "script", "other"]
    }
  }
]
Run Code Online (Sandbox Code Playgroud)

注入.js

class ScriptInterceptor {
    #observer
    #config = { childList: true, subtree: true };
    #handlerFn

    constructor(handlerFn) {
        this.#handlerFn = handlerFn;
        this.#observer = new MutationObserver(this.#callback.bind(this));
        this.#observer.observe(document, this.#config);
    }

    #isScriptMutation = (node) => { 
        return node.tagName === "SCRIPT";
    }

    #callback(mutationList, observer) {
        for(const mutation of mutationList) {
            for(const node of mutation.addedNodes) {
                if(this.#isScriptMutation(node)) {
                    this.#handlerFn(node);
                }
            }
        }
    }
}

const scriptTrace = "Object.defineProperty(window,'alert',{configurable:false,writable:false})";

const interceptor = new ScriptInterceptor((script) => {
    if(script.textContent.includes(scriptTrace)) {
        script.textContent = "";
        script.remove();
    }
});

const inject = () => {
    const fn = () => {

        // Alert actor
        const alert = () => { }

        alert.toString = () => { return "function alert() { [native code] }" }

        const antiTamperCheckFaker = {
            deleteProperty() {
                return true;
            },

            getPrototypeOf() {
                return () => { };
            }
        }

        const alertProxy = new Proxy(alert, antiTamperCheckFaker);
        window.alert = alertProxy;
        Object.defineProperty(window, 'alert', { configurable: false, writable: false });
    }

    const e = document.createElement('script');
    e.text = `(${fn.toString()})()`;
    document.documentElement.appendChild(e);
}

inject();
Run Code Online (Sandbox Code Playgroud)