我正在尝试迭代网站中定义的所有全局变量,但在这样做的过程中,我还获得了本机浏览器功能。
var numf=0; var nump=0; var numo=0;
for(var p in this) {
if(typeof(this[p]) === "function"){
numf+=1;
console.log(p+"()");
} else if(typeof p != 'undefined'){
nump+=1;
console.log(p);
} else {
numo+=1;
console.log(p);
}
}
有没有一种方法可以确定函数是浏览器固有的还是在脚本中创建的?
您可以在方法上调用继承的.toString()
函数并检查结果。本机方法将具有类似[native code]
的块。
if( this[p].toString().indexOf('[native code]') > -1 ) {
// yep, native in the browser
}
更新是因为很多评论员希望得到一些澄清,而人们确实对这样的检测有要求。为了让这个检查真正保存下来,我们可能应该使用一行这样的代码:
if( /{s+[native code]/.test( Function.prototype.toString.call( this[ p ] ) ) ) {
// yep, native
}
现在我们使用Function
的prototype
中的.toString
方法,这使得其他脚本不太可能覆盖toString
方法。其次,我们使用正则表达式进行检查,这样我们就不会被函数体中的注释所欺骗。
function isFuncNative(f) {
return !!f && (typeof f).toLowerCase() == 'function'
&& (f === Function.prototype
|| /^s*functions*(b[a-z$_][a-z0-9$_]*b)*s*((|([a-z$_][a-z0-9$_]*)(s*,[a-z$_][a-z0-9$_]*)*))s*{s*[native code]s*}s*$/i.test(String(f)));
}
这应该足够好了。该函数执行以下测试:
- 无效或未定义
- param实际上是一个函数
- param是Function.prototype本身(这是一种特殊情况,其中Function.prototype.toString给出
function Empty(){}
( - 函数体正是
function <valid_function_name> (<valid_param_list>) { [native code] }
regex有点复杂,但在我的4GB联想笔记本电脑(双核(上,它在chrome中运行得相当快:
var n = (new Date).getTime();
for (var i = 0; i < 1000000; i++) {
i%2 ? isFuncNative(isFuncNative) :
isFuncNative(document.getElementById);
};
(new Date).getTime() - n;
3023ms。因此,一旦全部为JIT’ed,函数运行大约需要3微秒。
它适用于所有浏览器。以前,我使用Function.prototype.toString.call,这会破坏IE,因为在IE中,DOM元素方法和窗口方法不是函数,而是对象,并且它们没有toString方法。字符串构造函数很好地解决了这个问题。
Function.prototype.toString
可以被欺骗,有点像这样:
Function.prototype.toString = (function(_toString){
return function() {
if (shouldSpoof) return 'function() { [native code] }'
return _toString.apply(this, arguments)
}
})(Function.prototype.toString)
您可以通过捕获.apply()
、.call()
、.bind()
(以及其他(来检测Function.prototype.toString
是否被破坏。
如果是,您可以从新注入的IFRAME
中获取Function.prototype.toString
的"干净"版本。
2022答案
既然我们有了代理API,就没有防故障的方法来确定本机函数是否被重写。
例如:
function isNativeFunction(f) {
return f.toString().includes("[native code]");
}
window.fetch = new Proxy(window.fetch, {
apply: function (target, thisArg, argumentsList) {
console.log("Fetch call intercepted:", ...argumentsList);
Reflect.apply(...arguments);
},
});
window.fetch.toString(); // → "function fetch() { [native code] }"
isNativeFunction(window.fetch); // → true
根据规范,代理对象应该与其目标无法区分。一些运行时(例如Node.js(提供了一些实用程序来违反规范并检查对象是否被代理,但在浏览器中,唯一的方法是在应用任何代理之前对代理API本身进行猴子补丁。
所以,回到最初的问题——我认为现在唯一可用的选择是引用"干净"的本机函数,然后将您潜在的猴子补丁函数与之进行比较:
<html>
<head>
<script>
// Store a reference of the original "clean" native function before any
// other script has a chance to modify it.
// In this case, we're just holding a reference of the original fetch API
// and hide it behind a closure. If you don't know in advance what API
// you'll want to check, you might need to store a reference to multiple
// `window` objects.
(function () {
const { fetch: originalFetch } = window;
window.__isFetchMonkeyPatched = function () {
return window.fetch !== originalFetch;
};
})();
// From now on, you can check if the fetch API has been monkey patched
// by invoking window.__isFetchMonkeyPatched().
//
// Example:
window.fetch = new Proxy(window.fetch, {
apply: function (target, thisArg, argumentsList) {
console.log("Fetch call intercepted:", ...argumentsList);
Reflect.apply(...arguments);
},
});
window.__isFetchMonkeyPatched(); // → true
</script>
</head>
</html>
通过使用严格的引用检查,我们避免了所有toString()
漏洞。它甚至适用于代理,因为它们无法捕获相等比较。
这种方法的主要缺点是可能不切实际。它需要在应用程序中运行任何其他代码之前存储原始函数引用(以确保它仍然完好无损(,而有时你无法做到这一点(例如,如果你正在构建库(。
为了获得更多信息,我最近写了一篇文章,深入探讨了确定本机函数是否是猴子补丁的可用方法。你可以在这里找到它。
对于想要使用这些检测方法来阻止用户行为(如userscript(的开发人员来说,实际上有一些方法可以绕过这些方法。
例如:
(以下isNative
功能的检测方法来自于过去其他人提供的方法。(
function isNative(f) {
if (!!/bound/.test(f.name)) return 1;
if (!!!/{s+[native code]/.test(Function.prototype.toString.call(f))) return 2;
if (!!!/{s+[native code]/.test(eval(f) + "")) return 3;
if ((typeof f).toLowerCase() !== 'function') return 4;
if (!/^s*functions*(b[a-z$_][a-z0-9$_]*b)*s*((|([a-z$_][a-z0-9$_]*)(s*,[a-z$_][a-z0-9$_]*)*))s*{s*[native code]s*}s*$/i.test(String(f))) return 5;
return true;
}
function fakeStringify(value) {
return `Fake Stringify ${value}`;
};
const s = new Proxy(fakeStringify, {
get(target, prop, receiver) {
if (prop === "name") {
return "stringify";
} else if (prop === Symbol.toPrimitive) {
return function () {
return "function () { [native code] }";
};
}
}
});
const obj = {
a: 1
};
console.log("========= [native] =========");
console.log(isNative(JSON.stringify));
console.log(JSON.stringify(obj));
console.log(JSON.stringify.name);
JSON.stringify = s;
console.log("======== [override] ========");
console.log(isNative(JSON.stringify));
console.log(JSON.stringify(obj));
console.log(JSON.stringify.name);
执行后可以发现,isNative
检测通过Proxy
后可以成功欺骗。
据我所知,在浏览器环境中没有办法检测Proxy fucntion
。但如果你知道如何检测它,请提供它,谢谢!
我尝试了一种不同的方法。这只针对firefox和chrome进行了测试。
function isNative(obj){
//Is there a function?
//You may throw an exception instead if you want only functions to get in here.
if(typeof obj === 'function'){
//Check does this prototype appear as an object?
//Most natives will not have a prototype of [object Object]
//If not an [object Object] just skip to true.
if(Object.prototype.toString.call(obj.prototype) === '[object Object]'){
//Prototype was an object, but is the function Object?
//If it's not Object it is not native.
//This only fails if the Object function is assigned to prototype.constructor, or
//Object function is assigned to the prototype, but
//why you wanna do that?
if(String(obj.prototype.constructor) !== String(Object.prototype.constructor)){
return false;
}
}
}
return true;
}
function bla(){}
isNative(bla); //false
isNative(Number); //true
isNative(Object); //true
isNative(Function); //true
isNative(RegExp); //true
几乎所有这些都将失败,因为:
function notNative(){}
notNative.toString = String.bind(0, "function notNative() { [native code] }");
console.log( notNative.toString() );
相反:
Function.prototype.isNative = function(){
return Function.prototype.toString.call(this).slice(-14, -3) === "native code";
};
console.log(alert.isNative());
console.log(String.isNative());
function foo(){}
console.log(foo.isNative());