开发者问题收集

如何访问被重写的对象的内置方法?

2018-12-30
1102

网页将内置的 javascript 方法设置为 null ,而我正尝试找到一种方法来调用用户脚本中的重写方法。

请考虑以下代码:

// Overriding the native method to something else
document.querySelectorAll = null;

现在,如果我尝试执行 document.querySelectorAll('#an-example') ,我将收到异常 Uncaught TypeError: null is not a function 。原因是该方法已更改为 null 并且不再可访问。

我正在寻找一种方法来以某种方式恢复用户脚本中对该方法的引用。问题是网站可以覆盖对任何内容的引用(甚至包括 DocumentElementObject 构造函数)。

由于网站也可以轻松地将引用设置为 null ,因此我需要一种方法来访问 网站无法覆盖 querySelectorAll 方法。

挑战在于, 任何方法 (例如 createElementgetElementsByTagName (除了它们的 prototype ))都可以在页面上执行我的用户脚本时被覆盖为 null

我的问题是,如果 Document HTMLDocument 构造函数方法也已被覆盖,我该如何访问它们?被覆盖了吗?


注意:

由于浏览器限制 Tampermonkey 无法在文档 开头 运行我的脚本,因此我无法保存对我想使用的方法的引用,如下所示:

// the following code cannot be run at the beginning of the document
var _originalQuerySelectorAll = document.querySelectorAll;
2个回答

至少有 3 种方法:

  1. 使用 userscript 沙盒 。可惜的是,由于 Tampermonkey 和 Violentmonkey 的设计缺陷/错误,该方法目前仅适用于 Greasemonkey(包括版本 4+)。详情见下文。
  2. 使用 @run-at document-start 。不过,该方法在快速页面上也不起作用。
  3. 删除函数覆盖 。该方法通常有效,但容易受到目标页面的更多干扰。如果页面更改了函数的 原型 ,则可以阻止它。


另请参阅 停止执行 Javascript 函数(客户端)或对其进行调整


请注意,下面的所有脚本和扩展示例都是 完整的工作代码
您可以通过更改以下内容针对 此 JS Bin 页面 对它们进行测试:
*://YOUR_SERVER.COM/YOUR_PATH/*
至:
https://output.jsbin.com/kobegen*



用户脚本沙盒:

这是首选方法,适用于 Firefox+Greasemonkey(包括 Greasemonkey 4)。

当将 @grant 设置为非 none 时,脚本引擎 应该 在浏览器专门为此目的提供的沙盒中运行脚本。

在适当的沙盒中,目标页面可以覆盖 document.querySelectorAll 或其他本机函数,而 无论如何,用户脚本都会看到自己的完全未受影响的实例

应该 始终有效:

// ==UserScript==
// @name     _Unoverride built in functions
// @match    *://YOUR_SERVER.COM/YOUR_PATH/*
// @grant    GM_addStyle
// @grant    GM.getValue
// ==/UserScript==
//- The @grant directives are needed to restore the proper sandbox.

console.log ("document.querySelectorAll: ", document.querySelectorAll);

并产生:

document.querySelectorAll: function querySelectorAll() { [native code] }

但是, Tampermonkey 和 Violentmonkey 都没有正确沙盒化 ,无论是在 Chrome 还是 Firefox 中。
即使打开了 Tampermonkey 或 Violentmonkey 版本的沙盒,目标页面也可以篡改 Tampermonkey 脚本看到的本机函数。
这不仅仅是一个设计缺陷,它还是 安全缺陷 和潜在漏洞的载体。

我们知道 Firefox 和 Chrome 并不是罪魁祸首是因为 (1) Greasemonkey-4 正确设置了沙盒,并且 (2) Chrome 扩展程序正确设置了“Isolated World”。也就是说,这个扩展程序:

ma​​nifest.json:

{
    "manifest_version": 2,
    "content_scripts": [ {
        "js":               [ "Unoverride.js" ],
        "matches":          [ "*://YOUR_SERVER.COM/YOUR_PATH/*" ]
    } ],
    "description":  "Unbuggers native function",
    "name":         "Native function restore slash use",
    "version":      "1"
}

Unoverride.js:

console.log ("document.querySelectorAll: ", document.querySelectorAll);

结果:

document.querySelectorAll: function querySelectorAll() { [native code] }

应该如此。



使用 @run-at document-start

从理论上讲,在 document-start 处运行脚本应该允许脚本在本机函数被更改之前捕获它。
例如:

// ==UserScript==
// @name     _Unoverride built in functions
// @match    *://YOUR_SERVER.COM/YOUR_PATH/*
// @grant    none
// @run-at   document-start
// ==/UserScript==

console.log ("document.querySelectorAll: ", document.querySelectorAll);

这有时在页面和/或网络速度足够慢的情况下有效。

但是,正如 OP 已经指出的那样, Tampermonkey 和 Violentmonkey 实际上都没有在任何其他页面代码之前注入和运行 ,因此此方法在快速页面上会失败。

请注意,在清单中使用 "run_at": "document_start" 设置的 Chrome 扩展程序内容脚本 确实 在正确的时间和/或足够快地运行。



删除函数覆盖:

如果页面(轻微)覆盖了 document.querySelectorAll 之类的函数,则可以使用 delete 清除覆盖,如下所示:

// ==UserScript==
// @name     _Unoverride built in functions
// @match    *://YOUR_SERVER.COM/YOUR_PATH/*
// @grant    none
// ==/UserScript==

delete document.querySelectorAll;

console.log ("document.querySelectorAll: ", document.querySelectorAll);

结果为:

document.querySelectorAll: function querySelectorAll() { [native code] }

缺点是:

  1. 如果页面更改了原型,则不起作用。例如:
    Document.prototype.querySelectorAll = null;
  2. 页面可以看到或重新进行此类更改,特别是如果您的脚本 触发得太早。

通过制作私人副本来缓解第 2 项:

// ==UserScript==
// @name     _Unoverride built in functions
// @match    *://YOUR_SERVER.COM/YOUR_PATH/*
// @grant    none
// ==/UserScript==

var foobarFunc = document.querySelectorAll;

delete document.querySelectorAll;

var _goodfunc = document.querySelectorAll;
var goodfunc  = function (params) {return _goodfunc.call (document, params); };

console.log (`goodfunc ("body"): `, goodfunc("body") );

结果为:

goodfunc ("body"): NodeList 1 0: body, length: 1,...

即使页面干扰了 document.querySelectorAllgoodfunc() 仍将继续工作(对于您的脚本)。

Brock Adams
2018-12-30

Tampermonkey中的其他解决方案是通过iframe恢复原始 - 假设该站点的 csp 允许它,通常这样做。 P.S.如果页面不透彻,则可以通过原型访问原型,而无需iframe技巧:

987278086
woxxom
2019-01-02