如何访问被覆盖的对象的内置方法?

时间:2018-12-30 12:57:21

标签: javascript object override userscripts tampermonkey

一个网页正在将内置的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,无法再访问。

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

由于该网站还可以轻松地将引用设置为Object,因此我需要一种方法来找到一种访问null方法的方法,该方法该网站将无法覆盖< / strong>。

挑战在于,任何方法(例如querySelectorAllcreateElement(除了它们的getElementsByTagName之外)都将被覆盖为prototype此时,我的用户脚本已在页面上执行。

我的问题是,如果还重写了 null Document 构造函数方法,该如何使用呢?


注意:

由于Tampermonkey due to browser limitations 无法在文档的开始上运行脚本,因此我无法保存对所需方法的引用这样使用:

HTMLDocument

2 个答案:

答案 0 :(得分:3)

至少有3种方法:

  1. 使用用户脚本沙箱。遗憾的是,由于Tampermonkey和Violentmonkey的设计缺陷/错误,该功能目前仅适用于Greasemonkey(包括版本4+)。下面有更多内容。
  2. 使用@run-at document-start。除此之外,这也不适用于快速页面。
  3. 删除功能替代。这通常是可行的,但容易对目标页面造成干扰。并且如果页面更改了功能的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(){[本地代码]}

缺点是:

  1. 如果页面更改了原型,将无法使用。 EG:
    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(“主体”):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, '*')