Facebook如何禁用浏览器的集成开发者工具?

1782

最近因为诈骗事件,开发者工具被人利用来发布垃圾信息,甚至用于“黑客”账户。Facebook已经封锁了开发者工具,我甚至不能使用控制台。

图片描述

他们是怎么做到的?一篇Stack Overflow的帖子声称不可能,但Facebook证明了他们是错的。

只需进入Facebook并打开开发者工具,将一个字符键入控制台,就会弹出此警告。无论您输入什么内容,都不会被执行。

这是如何实现的?

他们甚至阻止了控制台中的自动补全功能:

图片描述


27
仅供娱乐:console.log = function() {}。(注:该语句将 JavaScript 中的 console.log 函数重置为空函数,因此不会输出任何信息。) - tnt-rox
1
你找到他们如何阻止控制台自动完成功能的解决方案了吗? - Akshay Hegde
2
@AkshayHegde 这是由于阻止开发工具中的任何代码执行而引起的副作用。 - Derek 朕會功夫
@Derek朕會功夫,你能否分享一下代碼? - Akshay Hegde
1
只是提供信息,它在Chrome中不再被阻止了。 - John Lord
现在不再被阻止了,Chrome 等已经修复了开发者执行此操作的能力。 - Ryan Mann
14个回答

2533
我是Facebook的安全工程师,这是我的错误。我们正在为一些用户测试这个功能,以查看它是否可以减缓某些攻击,其中用户被欺骗将(恶意)JavaScript代码粘贴到浏览器控制台中。
需要明确的是:通常尝试在客户端阻止黑客是一个 坏主意; 这是为了防止 特定社交工程攻击
如果您进入测试组并感到恼怒,请原谅。 我尽力使旧的退出页面(现在是帮助页面)尽可能简单,同时仍然足够可怕,以停止至少 一些 受害者。
实际代码与 @joeldixon66的链接 相当相似;我们的代码更加复杂,没有任何好处。
Chrome将所有控制台代码包装在内。
with ((console && console._commandLineAPI) || {}) {
  <code goes here>
}

...因此,该网站重新定义console._commandLineAPI以抛出异常:

Object.defineProperty(console, '_commandLineAPI',
   { get : function() { throw 'Nooo!' } })

这还不太够(试试看!),但那是主要的技巧。
结语:Chrome团队决定从用户端的JS中打败控制台是一个漏洞,并修复了这个问题,使得这种技术无效。此后,还添加了额外的保护措施,保护用户免受自我XSS攻击

12
Chrome进行了更新,但这个人又修复了它:http://kspace.in/blog/2014/06/21/revisiting-disable-javascript-execution-from-console/ - Roger Gajraj
192
请不要因为某些用户的愚蠢而破坏开发者工具。这样的"解决方案"让我怒火万丈。 - Jonathan Dunlap
3
Chrome团队已经修复了它 - Derek 朕會功夫
109
我认为谷歌需要发布一个“安全”版本的Chrome,没有开发者工具,并强制所有自动更新的用户切换到这个版本。任何确实注意到区别并需要开发者工具的开发者都应该下载“可怕”的版本。事实上,在下载页面上直接标注它们为“可怕”和“安全”,并通过明确说明“你可能在这里是因为社交工程攻击告诉你下载了可怕的版本,请不要这么做”来防止达尔文主义者自我伤害。感谢FB开发人员如此有创意! - MonkeyZeus
13
那个警告信息只是一条“控制台日志”。 - gcampbell
显示剩余17条评论

98

我使用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);

由此,控制台自动完成会悄无声息地失败,而在控制台中输入的语句将无法执行(异常将被记录)。

参考资料:


49

很酷,但仍然覆盖相同的 window.console.log = function(){//empty} 并使用 console.log。 - super cool

34
除了重新定义console._commandLineAPI之外,在WebKit浏览器上还有其他方法可以进入InjectedScriptHost,以防止或更改输入到开发人员控制台的表达式的评估。
编辑:
Chrome在过去的版本中已经修复了这个问题。- 这一定是在2015年2月之前,因为我当时创建了这个代码片段。
所以这里有另一个可能性。这次我们直接钩入InjectedScript而不是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],你期望以下输出:

enter image description here

在monkeypatching InjectedScript._evaluateAndWrap 评估相同的表达式之后,将提供以下输出:

enter image description here

如你所见,指示输出结果的小左箭头仍然存在,但是这次我们得到了一个对象。表达式结果,数组[1,2,3,4]被表示为具有所有属性描述的对象。

我建议尝试评估这个和那个表达式,包括那些生成错误的表达式。它非常有趣。

此外,请注意is - InjectedScriptHost - 对象。它提供了一些方法用于玩耍并深入了解检查器的内部。

当然,你可以拦截所有这些信息并仍然向用户返回原始结果。

只需将else路径中的return语句替换为console.log(res),然后加上return res。然后你会得到以下结果。

enter image description here

编辑结束


这是被谷歌修复的以前版本。因此不再是一个可行的方法。

其中之一是钩入Function.prototype.call

Chrome通过使用InjectedScriptHost作为thisArg调用其eval函数来评估输入的表达式:

var result = evalFunction.call(object, expression);

基于此,你可以监听evaluate作为callthisArg,并获取对第一个参数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编译器处理。


29

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,以抛出安全错误。


27

自从Facebook能够做到这一点, 它就变得可能. 不是实际的Web开发人员工具,而是在控制台中执行Javascript.

看看这个: Facebook如何禁用浏览器集成的开发者工具?

虽然这真的没有多大效果,因为有其他方法可以绕过此类客户端安全性。

当您说它是客户端时,它发生在服务器控制之外,所以您不能对其做太多事情。如果您想知道为什么Facebook仍在这样做,那么这并不是为了安全,而是为了保护不懂JavaScript的普通用户免受运行代码(他们不知道如何阅读)的控制台。 这在许多网站上都很常见,在您完成他们要求您完成的操作后,它们承诺提供自动喜欢服务或其他Facebook功能机器人,在大多数情况下,它们会向您提供一小段JavaScript代码以在控制台中运行。

如果您没有像Facebook一样多的用户,那么我认为没有必要做Facebook正在做的事情。

即使您在控制台中禁用了JavaScript,在地址栏中运行JavaScript仍然是可能的。

输入图像描述

输入图像描述

如果浏览器在地址栏中禁用了JavaScript,则仍可以通过检查元素将JavaScript粘贴到其中一个链接中。

检查锚点:

输入图像描述

将代码粘贴到href中:

在此输入图片描述

在此输入图片描述

在此输入图片描述

关键是先进行服务器端的验证和安全性保护,然后再进行客户端的操作。


14

自从 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(){};
}

13

我的方法很简单,但可以帮助更深入地探讨这个主题。 列出所有的方法并将它们修改为无用的。

  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();        
})();

5

在内部,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);
});


1
这很不错,但它也会导致页面崩溃。 - Derek 朕會功夫
@Derek朕会功夫 我发现唯一的方法来抑制进一步的用户输入。 - Sam Denty
我想知道是否可以抛出一个错误而不是使用无限循环。编辑:已测试,不起作用。 - Derek 朕會功夫
@Derek,它在try catch塊中。你可能可以覆蓋塊上面的函數,但這只會防止自動完成(而不是評估)。 - Sam Denty
很遗憾,它无法工作。但我仍然可以通过在Chrome上按->三个点->更多工具->开发者工具来使用开发工具。 - Paul Grei

1
我这里有一个简单的方法: window.console = function () {} 它可以禁用浏览器控制台输出。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接