如何访问被重写的对象的内置方法?
How can I access built-in methods of an Object that is overridden?
一个网页正在将内置 javascript 方法设置为 null
,我正在尝试找到一种在用户脚本中调用覆盖方法的方法。
考虑以下代码:
// Overriding the native method to something else
document.querySelectorAll = null;
现在,如果我尝试执行 document.querySelectorAll('#an-example')
,我将得到异常 Uncaught TypeError: null is not a function
。原因是该方法已更改为 null
且无法再访问。
我正在寻找一种方法来以某种方式恢复对用户脚本中方法的引用。问题在于该网站可以覆盖对任何内容的引用(甚至包括 Document
、Element
和 Object
构造函数)。
由于网站也可以很方便的设置对null
的引用,所以我需要一个方法想办法访问网站不会的querySelectorAll
方法能够覆盖.
挑战在于 任何方法 例如 createElement
和 getElementsByTagName
(除了它们的 prototype
s)可以被覆盖到null
在页面上执行我的用户脚本时。
我的问题是,如何访问 Document
或 HTMLDocument
构造方法,如果它们也被覆盖了?
注:
由于 Tampermonkey due to browser limitations 不能 运行 我的脚本位于文档的 beginning ,我无法保存对我想使用的方法的引用,如下所示:
// the following code cannot be run at the beginning of the document
var _originalQuerySelectorAll = document.querySelectorAll;
至少有3种方法:
- 使用用户脚本沙箱。 las,由于 Tampermonkey 和 Violentmonkey 设计缺陷/错误,这目前仅适用于 Greasemonkey(包括版本 4+)。更多内容在下方。
- 使用
@run-at document-start
。除了这也不适用于快速页面。
- 删除函数覆盖。这通常有效,但更容易干扰 with/from 目标页面。如果页面更改了函数的
prototype
,则可能会被阻止。
另见 Stop execution of Javascript function (client side) or tweak it
请注意,下面的所有脚本和扩展示例都是 完整的工作代码。
您可以通过更改 this JS Bin page 来测试它们:
*://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 脚本看到的本机功能,即使启用了 Tampermonkey 或 Violentmonkey 版本的沙箱。
这不仅仅是一个设计缺陷,它是 一个安全缺陷 和潜在攻击的媒介。
我们知道 Firefox 和 Chrome 不是罪魁祸首,因为 (1) Greasemonkey-4 正确设置了沙箱,(2) Chrome 扩展设置了 "Isolated World" 适当地。也就是这个扩展名:
manifest.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);
这有时适用于足够慢的页面 and/or 网络。
但是,正如 OP 已经指出的那样,Tampermonkey 和 Violentmonkey 实际上都没有在任何其他页面代码 之前注入和 运行,因此此方法在快速页面上失败。
请注意,Chrome-扩展内容脚本在清单中设置了 "run_at": "document_start"
,在正确的时间 运行 and/or够快了。
删除函数覆盖:
如果页面(轻微地)覆盖了像 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] }
缺点是:
- 如果页面更改了原型,将无法正常工作。例如:
Document.prototype.querySelectorAll = null;
- 该页面可以看到或重新进行此类更改,尤其是如果您的脚本
也很快.
通过制作私人副本缓解第 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"): NodeList10: body, length: 1,...
并且 goodfunc()
将继续工作(对于您的脚本),即使该页面 remolests document.querySelectorAll
。
Tampermonkey 中的其他解决方案是通过 iframe 恢复原始文件 - 假设站点的 CSP 允许它,AFAIK 通常会这样做。
const builtin = new Proxy(document.createElement('iframe'), {
get(frame, p) {
if (!frame.parentNode) {
frame.style.cssText = 'display:none !important';
document.documentElement.appendChild(frame);
}
return frame.contentWindow[p];
}
});
用法:
console.log(builtin.document.querySelectorAll.call(document, '*'));
P.S。如果页面不完整,您可以通过没有 iframe 技巧的原型访问原始页面:
Document.prototype.querySelectorAll.call(document, '*')
Element.prototype.querySelectorAll.call(normalElements, '*')
一个网页正在将内置 javascript 方法设置为 null
,我正在尝试找到一种在用户脚本中调用覆盖方法的方法。
考虑以下代码:
// Overriding the native method to something else
document.querySelectorAll = null;
现在,如果我尝试执行 document.querySelectorAll('#an-example')
,我将得到异常 Uncaught TypeError: null is not a function
。原因是该方法已更改为 null
且无法再访问。
我正在寻找一种方法来以某种方式恢复对用户脚本中方法的引用。问题在于该网站可以覆盖对任何内容的引用(甚至包括 Document
、Element
和 Object
构造函数)。
由于网站也可以很方便的设置对null
的引用,所以我需要一个方法想办法访问网站不会的querySelectorAll
方法能够覆盖.
挑战在于 任何方法 例如 createElement
和 getElementsByTagName
(除了它们的 prototype
s)可以被覆盖到null
在页面上执行我的用户脚本时。
我的问题是,如何访问 Document
或 HTMLDocument
构造方法,如果它们也被覆盖了?
注:
由于 Tampermonkey due to browser limitations 不能 运行 我的脚本位于文档的 beginning ,我无法保存对我想使用的方法的引用,如下所示:
// the following code cannot be run at the beginning of the document
var _originalQuerySelectorAll = document.querySelectorAll;
至少有3种方法:
- 使用用户脚本沙箱。 las,由于 Tampermonkey 和 Violentmonkey 设计缺陷/错误,这目前仅适用于 Greasemonkey(包括版本 4+)。更多内容在下方。
- 使用
@run-at document-start
。除了这也不适用于快速页面。 - 删除函数覆盖。这通常有效,但更容易干扰 with/from 目标页面。如果页面更改了函数的
prototype
,则可能会被阻止。
另见 Stop execution of Javascript function (client side) or tweak it
请注意,下面的所有脚本和扩展示例都是 完整的工作代码。
您可以通过更改 this JS Bin page 来测试它们:
*://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 脚本看到的本机功能,即使启用了 Tampermonkey 或 Violentmonkey 版本的沙箱。
这不仅仅是一个设计缺陷,它是 一个安全缺陷 和潜在攻击的媒介。
我们知道 Firefox 和 Chrome 不是罪魁祸首,因为 (1) Greasemonkey-4 正确设置了沙箱,(2) Chrome 扩展设置了 "Isolated World" 适当地。也就是这个扩展名:
manifest.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);
这有时适用于足够慢的页面 and/or 网络。
但是,正如 OP 已经指出的那样,Tampermonkey 和 Violentmonkey 实际上都没有在任何其他页面代码 之前注入和 运行,因此此方法在快速页面上失败。
请注意,Chrome-扩展内容脚本在清单中设置了 "run_at": "document_start"
,在正确的时间 运行 and/or够快了。
删除函数覆盖:
如果页面(轻微地)覆盖了像 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] }
缺点是:
- 如果页面更改了原型,将无法正常工作。例如:
Document.prototype.querySelectorAll = null;
- 该页面可以看到或重新进行此类更改,尤其是如果您的脚本 也很快.
通过制作私人副本缓解第 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"): NodeList10: body, length: 1,...
并且 goodfunc()
将继续工作(对于您的脚本),即使该页面 remolests document.querySelectorAll
。
Tampermonkey 中的其他解决方案是通过 iframe 恢复原始文件 - 假设站点的 CSP 允许它,AFAIK 通常会这样做。
const builtin = new Proxy(document.createElement('iframe'), {
get(frame, p) {
if (!frame.parentNode) {
frame.style.cssText = 'display:none !important';
document.documentElement.appendChild(frame);
}
return frame.contentWindow[p];
}
});
用法:
console.log(builtin.document.querySelectorAll.call(document, '*'));
P.S。如果页面不完整,您可以通过没有 iframe 技巧的原型访问原始页面:
Document.prototype.querySelectorAll.call(document, '*')
Element.prototype.querySelectorAll.call(normalElements, '*')