一个网页正在将内置的javascript方法设置为 var counter = 1;
$('#addRow').on( 'click', function () {
table.row.add( [
counter +'.1',
counter +'.2',
counter +'.3',
counter +'.4'
]).draw( false );
counter++;
} );
// Automatically add a first row of data
$('#addRow').click();
,我正在尝试找到一种方法来调用用户脚本中的覆盖方法。
考虑以下代码:
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
方法的方法,该方法该网站将无法覆盖< / strong>。
挑战在于,任何方法(例如querySelectorAll
和createElement
(除了它们的getElementsByTagName
之外)都将被覆盖为prototype
此时,我的用户脚本已在页面上执行。
我的问题是,如果还重写了 null
或 Document
构造函数方法,该如何使用呢?
由于Tampermonkey due to browser limitations 无法在文档的开始上运行脚本,因此我无法保存对所需方法的引用这样使用:
HTMLDocument
答案 0 :(得分:3)
至少有3种方法:
@run-at document-start
。除此之外,这也不适用于快速页面。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
设置为无时,脚本引擎会被设置为在浏览器专门为此目的提供的沙箱中运行脚本。
在适当的沙箱中,目标页面可以覆盖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:函数querySelectorAll(){[本地代码]}
但是,无论在Chrome还是Firefox中, Tampermonkey和Violentmonkey都无法正确沙箱。
即使打开了Tampermonkey或Violentmonkey的沙箱版本,目标页面也可能篡改Tampermonkey脚本看到的本机功能。
这不仅是设计缺陷,而且是安全缺陷,也是潜在漏洞的载体。
我们知道Firefox和Chrome并不是罪魁祸首,因为(1)Greasemonkey-4正确设置了沙箱,并且(2)Chrome扩展程序正确设置了“孤立的世界”。也就是说,此扩展名:
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:函数querySelectorAll(){[本地代码]}
应有的
@run-at document-start
:从理论上讲,在document-start
运行脚本应允许脚本在更改本机函数之前捕获本机函数。
EG:
// ==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:函数querySelectorAll(){[本地代码]}
缺点是:
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(“主体”):NodeList 1 0:主体,长度:1,...
并且goodfunc()
将继续为您的脚本工作(即使您的页面改写了document.querySelectorAll
。
答案 1 :(得分:2)
Tampermonkey中的其他解决方案是通过iframe恢复原始文件-假设该网站的CSP允许这样做(通常是AFAIK)。
function restoreOriginal(name) {
let f = restoreOriginal.iframe;
if (f) {
let o = f.contentWindow;
for (const prop of name.split('.'))
o = o[prop];
return Promise.resolve(o);
} else {
return new Promise((resolve, reject) => {
f = restoreOriginal.iframe = document.createElement('iframe');
f.style.cssText = 'display:none !important';
f.onload = () => {
f.onload = null;
resolve(restoreOriginal(name));
};
f.onerror = reject;
document.documentElement.appendChild(f);
});
}
}
用法:
(async () => {
const dqsa = await restoreOriginal('document.querySelectorAll');
// restore
document.querySelectorAll = dqsa;
// or just call it directly
console.log(dqsa.call(document, '*'));
})();
P.S。如果页面不完整,则可以通过原型访问原始页面:
Document.prototype.querySelectorAll.call(document, '*')
Element.prototype.querySelectorAll.call(normalElements, '*')