最近因为诈骗事件,开发者工具被人利用来发布垃圾信息,甚至用于“黑客”账户。Facebook已经封锁了开发者工具,我甚至不能使用控制台。
他们是怎么做到的?一篇Stack Overflow的帖子声称不可能,但Facebook证明了他们是错的。
只需进入Facebook并打开开发者工具,将一个字符键入控制台,就会弹出此警告。无论您输入什么内容,都不会被执行。
这是如何实现的?
他们甚至阻止了控制台中的自动补全功能:
最近因为诈骗事件,开发者工具被人利用来发布垃圾信息,甚至用于“黑客”账户。Facebook已经封锁了开发者工具,我甚至不能使用控制台。
他们是怎么做到的?一篇Stack Overflow的帖子声称不可能,但Facebook证明了他们是错的。
只需进入Facebook并打开开发者工具,将一个字符键入控制台,就会弹出此警告。无论您输入什么内容,都不会被执行。
这是如何实现的?
他们甚至阻止了控制台中的自动补全功能:
with ((console && console._commandLineAPI) || {}) {
<code goes here>
}
...因此,该网站重新定义console._commandLineAPI
以抛出异常:
Object.defineProperty(console, '_commandLineAPI',
{ get : function() { throw 'Nooo!' } })
我使用Chrome开发工具找到了Facebook的控制台破解脚本。以下是经过些许修改以提高可读性后的脚本。我已删除了我无法理解的部分:
Object.defineProperty(window, "console", {
value: console,
writable: false,
configurable: false
});
var i = 0;
function showWarningAndThrow() {
if (!i) {
setTimeout(function () {
console.log("%cWarning message", "font: 2em sans-serif; color: yellow; background-color: red;");
}, 1);
i = 1;
}
throw "Console is disabled";
}
var l, n = {
set: function (o) {
l = o;
},
get: function () {
showWarningAndThrow();
return l;
}
};
Object.defineProperty(console, "_commandLineAPI", n);
Object.defineProperty(console, "__commandLineAPI", n);
由此,控制台自动完成会悄无声息地失败,而在控制台中输入的语句将无法执行(异常将被记录)。
参考资料:
我无法在任何页面上触发它。更健壮的版本可以这样实现:
window.console.log = function(){
console.error('The developer console is temp...');
window.console.log = function() {
return false;
}
}
console.log('test');
要为输出样式化:JavaScript控制台中的颜色
编辑认为@joeldixon66的想法是正确的:从控制台禁用JavaScript执行 « ::: KSpace :::
window.console.log = function(){//empty}
并使用 console.log。 - super coolconsole._commandLineAPI
之外,在WebKit浏览器上还有其他方法可以进入InjectedScriptHost,以防止或更改输入到开发人员控制台的表达式的评估。InjectedScript._evaluateAndWrap
而无需依赖于InjectedScriptHost.evaluate
,因为这可以让您更精细地控制应该发生什么。var is;
Object.defineProperty(Object.prototype,"_lastResult",{
get:function(){
return this._lR;
},
set:function(v){
if (typeof this._commandLineAPIImpl=="object") is=this;
this._lR=v;
}
});
setTimeout(function(){
var ev=is._evaluateAndWrap;
is._evaluateAndWrap=function(){
var res=ev.apply(is,arguments);
console.log();
if (arguments[2]==="completion") {
//This is the path you end up when a user types in the console and autocompletion get's evaluated
//Chrome expects a wrapped result to be returned from evaluateAndWrap.
//You can use `ev` to generate an object yourself.
//In case of the autocompletion chrome exptects an wrapped object with the properties that can be autocompleted. e.g.;
//{iGetAutoCompleted: true}
//You would then go and return that object wrapped, like
//return ev.call (is, '', '({test:true})', 'completion', true, false, true);
//Would make `test` pop up for every autocompletion.
//Note that syntax as well as every Object.prototype property get's added to that list later,
//so you won't be able to exclude things like `while` from the autocompletion list,
//unless you wou'd find a way to rewrite the getCompletions function.
//
return res; //Return the autocompletion result. If you want to break that, return nothing or an empty object
} else {
//This is the path where you end up when a user actually presses enter to evaluate an expression.
//In order to return anything as normal evaluation output, you have to return a wrapped object.
//In this case, we want to return the generated remote object.
//Since this is already a wrapped object it would be converted if we directly return it. Hence,
//`return result` would actually replicate the very normal behaviour as the result is converted.
//to output what's actually in the remote object, we have to stringify it and `evaluateAndWrap` that object again.`
//This is quite interesting;
return ev.call (is, null, '(' + JSON.stringify (res) + ')', "console", true, false, true)
}
};
},0);
这段代码有点啰嗦,但我觉得加上一些注释比较好
通常情况下,如果一个用户评估例如[1,2,3,4]
,你期望以下输出:
在monkeypatching InjectedScript._evaluateAndWrap
评估相同的表达式之后,将提供以下输出:
如你所见,指示输出结果的小左箭头仍然存在,但是这次我们得到了一个对象。表达式结果,数组[1,2,3,4]
被表示为具有所有属性描述的对象。
我建议尝试评估这个和那个表达式,包括那些生成错误的表达式。它非常有趣。
此外,请注意is
- InjectedScriptHost
- 对象。它提供了一些方法用于玩耍并深入了解检查器的内部。
当然,你可以拦截所有这些信息并仍然向用户返回原始结果。
只需将else路径中的return语句替换为console.log(res)
,然后加上return res
。然后你会得到以下结果。
编辑结束
这是被谷歌修复的以前版本。因此不再是一个可行的方法。
其中之一是钩入Function.prototype.call
Chrome通过使用InjectedScriptHost
作为thisArg
调用其eval函数来评估输入的表达式:
var result = evalFunction.call(object, expression);
基于此,你可以监听evaluate
作为call
的thisArg
,并获取对第一个参数InjectedScriptHost
的引用
if (window.URL) {
var ish, _call = Function.prototype.call;
Function.prototype.call = function () { //Could be wrapped in a setter for _commandLineAPI, to redefine only when the user started typing.
if (arguments.length > 0 && this.name === "evaluate" && arguments [0].constructor.name === "InjectedScriptHost") { //If thisArg is the evaluate function and the arg0 is the ISH
ish = arguments[0];
ish.evaluate = function (e) { //Redefine the evaluation behaviour
throw new Error ('Rejected evaluation of: \n\'' + e.split ('\n').slice(1,-1).join ("\n") + '\'');
};
Function.prototype.call = _call; //Reset the Function.prototype.call
return _call.apply(this, arguments);
}
};
}
你可以丢出一个错误,表明评估被拒绝。
这里有一个示例,其中输入的表达式在传递给evaluate
函数之前会先经过CoffeeScript编译器处理。
Netflix也实现了这个功能。
(function() {
try {
var $_console$$ = console;
Object.defineProperty(window, "console", {
get: function() {
if ($_console$$._commandLineAPI)
throw "Sorry, for security reasons, the script console is deactivated on netflix.com";
return $_console$$
},
set: function($val$$) {
$_console$$ = $val$$
}
})
} catch ($ignore$$) {
}
})();
他们只是覆盖了console._commandLineAPI
,以抛出安全错误。
自从Facebook能够做到这一点, 它就变得可能. 不是实际的Web开发人员工具,而是在控制台中执行Javascript.
看看这个: Facebook如何禁用浏览器集成的开发者工具?
虽然这真的没有多大效果,因为有其他方法可以绕过此类客户端安全性。
当您说它是客户端时,它发生在服务器控制之外,所以您不能对其做太多事情。如果您想知道为什么Facebook仍在这样做,那么这并不是为了安全,而是为了保护不懂JavaScript的普通用户免受运行代码(他们不知道如何阅读)的控制台。 这在许多网站上都很常见,在您完成他们要求您完成的操作后,它们承诺提供自动喜欢服务或其他Facebook功能机器人,在大多数情况下,它们会向您提供一小段JavaScript代码以在控制台中运行。
如果您没有像Facebook一样多的用户,那么我认为没有必要做Facebook正在做的事情。
即使您在控制台中禁用了JavaScript,在地址栏中运行JavaScript仍然是可能的。
如果浏览器在地址栏中禁用了JavaScript,则仍可以通过检查元素将JavaScript粘贴到其中一个链接中。
检查锚点:
将代码粘贴到href中:
关键是先进行服务器端的验证和安全性保护,然后再进行客户端的操作。
自从 Facebook 可以禁用控制台,Chrome 发生了很多变化...
从 2017 年 3 月起,这种方法已经无效了。
你所能做的最好的事情就是禁用一些控制台功能,例如:
if(!window.console) window.console = {};
var methods = ["log", "debug", "warn", "info", "dir", "dirxml", "trace", "profile"];
for(var i=0;i<methods.length;i++){
console[methods[i]] = function(){};
}
我的方法很简单,但可以帮助更深入地探讨这个主题。 列出所有的方法并将它们修改为无用的。
Object.getOwnPropertyNames(console).filter(function(property) {
return typeof console[property] == 'function';
}).forEach(function (verb) {
console[verb] =function(){return 'Sorry, for security reasons...';};
});
然而,更好的方法是以任何有意义的方式禁用开发者工具的打开
(function() {
'use strict';
Object.getOwnPropertyNames(console).filter(function(property) {
return typeof console[property] == 'function';
}).forEach(function (verb) {
console[verb] =function(){return 'Sorry, for security reasons...';};
});
window.addEventListener('devtools-opened', ()=>{
// do some extra code if needed or ...
// maybe even delete the page, I still like to add redirect just in case
window.location.href+="#";
window.document.head.innerHTML="";
window.document.body.innerHTML="devtools, page is now cleared";
});
window.addEventListener('devtools-closed', ()=>{
// do some extra code if needed
});
let verifyConsole = () => {
var before = new Date().getTime();
debugger;
var after = new Date().getTime();
if (after - before > 100) { // user had to resume the script manually via opened dev tools
window.dispatchEvent(new Event('devtools-opened'));
}else{
window.dispatchEvent(new Event('devtools-closed'));
}
setTimeout(verifyConsole, 100);
}
verifyConsole();
})();
在内部,devtools将一个名为getCompletions
的IIFE注入到页面中,在Devtools控制台中按下键时调用。
查看该函数源代码,它使用了一些全局函数,这些函数可以被覆盖。
通过使用Error
构造函数,可以获取调用堆栈,其中将包括由Devtools调用的getCompletions
。
示例:
const disableDevtools = callback => {
const original = Object.getPrototypeOf;
Object.getPrototypeOf = (...args) => {
if (Error().stack.includes("getCompletions")) callback();
return original(...args);
};
};
disableDevtools(() => {
console.error("devtools has been disabled");
while (1);
});
window.console = function () {}
它可以禁用浏览器控制台输出。