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

因此,显然是由于最近的骗局,开发人员工具被人们用来发布垃圾邮件,甚至被用来 “破解” 帐户。 Facebook 阻止了开发人员工具,我什至不能使用该控制台。

在此处输入图片说明

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

只需转到 Facebook 并打开开发人员工具,在控制台中键入一个字符,就会弹出此警告。不管您输入什么内容,都不会执行它。

这怎么可能?

他们甚至阻止了控制台中的自动完成:

在此处输入图片说明

答案

我是 Facebook 的安全工程师,这是我的错。我们正在为某些用户进行测试,以查看它是否可以减慢某些诱使用户将(恶意)JavaScript 代码粘贴到浏览器控制台的攻击。

只是要清楚一点:尝试阻止客户端的黑客通常是一个坏主意 。这是为了防止特定的社会工程攻击

如果您最终参加了测试小组并为此感到烦恼,请对不起。我试图使旧的退出页面(现在是帮助页面 )尽可能简单,同时仍然令人恐惧,无法阻止至少一些受害者。

实际的代码与@ joeldixon66 的链接非常相似;没有充分的理由,我们的情况会稍微复杂一些。

Chrome 将所有控制台代码包装在其中

with ((console && console._commandLineAPI) || {}) {
  <code goes here>
}

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

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

这还不够(尝试一下!) ,但这是主要技巧。


结语:Chrome 小组认为从用户端 JS 击败控制台是一个错误,并解决了该问题 ,使该技术无效。之后,添加了附加保护以保护用户免受 self-xss 的攻击

我使用 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有一个正确的主意: 从控制台 «::: KSpace ::: 禁用 JavaScript 执行

除了重新定义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]您将期望获得以下输出:

在此处输入图片说明

在 monkeypatching InjectedScript._evaluateAndWrap评估了完全相同的表达式之后,给出以下输出:

在此处输入图片说明

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

我建议尝试对此表达式进行评估,包括那些会产生错误的表达式。这很有趣。

另外,看看is - InjectedScriptHost对象。它提供了一些可以使用的方法,并可以对检查器的内部结构有一些了解。

当然,您可以截取所有这些信息,并且仍将原始结果返回给用户。

只需通过替换其他路径 return 语句console.log (res)之后的return res 。然后,您将得到以下结果。

在此处输入图片说明

编辑结束


这是 Google 修复的先前版本。因此,不再有可能。

其中之一是挂接到Function.prototype.call

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

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

鉴于此,您可以侦听正在evaluatecallthisArg ,并获取对第一个参数的引用( 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,(当您将代码粘贴到 Google Chrome 浏览器的地址栏中时,它会删除短语 “javascript:”),仍然可以通过 inspect 元素将 javascript 粘贴到链接之一中。

检查锚点:

在此处输入图片说明

将代码粘贴到 href 中:

在此处输入图片说明

在此处输入图片说明

在此处输入图片说明

最重要的是应该首先进行服务器端验证和安全性,然后再进行客户端验证。

我的简单方法,但是可以帮助在此主题上进行进一步的更改。列出所有方法并将其更改为无用。

Object.getOwnPropertyNames(console).filter(function(property) {
     return typeof console[property] == 'function';
  }).forEach(function (verb) {
     console[verb] =function(){return 'Sorry, for security reasons...';};
  });

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

内部 devtools 名为内喷射的 IIFE getCompletions到网页中,当一个键被按下的 Devtools 控制台内部调用。

查看该函数来源 ,它使用了一些可以覆盖的全局函数。

通过使用Error的构造有可能获得调用堆栈,其中包括getCompletions时 Devtools 调用。


例:

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

一个简单的解决方案!

setInterval(()=>console.clear(),1500);