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

时间:2014-02-11 03:42:09

标签: javascript facebook google-chrome-devtools

显然,由于最近的骗局,开发人员工具被人们利用来发布垃圾邮件,甚至用来“破解”帐户。 Facebook已经阻止了开发人员工具,我甚至无法使用控制台。

Enter image description here

他们是怎么做到的? One Stack Overflow post claimed that it is not possible,但Facebook证明他们错了。

只需转到Facebook并打开开发人员工具,在控制台中键入一个字符,然后弹出此警告。无论你投入什么,它都不会被执行。

这怎么可能?

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

Enter image description here

13 个答案:

答案 0 :(得分:2363)

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

要明确一点:试图阻止黑客客户端一般是bad idea; 这是为了防范specific social engineering attack

如果你最终进入测试组并对此感到恼火,抱歉。 我试图让旧的退出页面(现在help page)尽可能简单,同时仍然可怕至少阻止一些的受害者。

实际代码与@joeldixon66's link非常相似;我们的情况有点复杂,没有充分的理由。

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

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

...所以网站重新定义console._commandLineAPI投掷:

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

这是not quite enough (try it!),但就是这样 主要技巧。


结语:Chrome团队决定从用户端JS中取消控制台是一个错误fixed the issue,导致此技术无效。之后,protect users from self-xss增加了额外的保护。

答案 1 :(得分:78)

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

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

参考文献:

答案 2 :(得分:40)

我无法在任何页面上触发它。一个更强大的版本可以做到这一点:

window.console.log = function(){
    console.error('The developer console is temp...');
    window.console.log = function() {
        return false;
    }
}

console.log('test');

设置输出样式:Colors in JavaScript console

修改思考@joeldixon66有正确的想法:Disable JavaScript execution from console « ::: KSpace :::

答案 3 :(得分:28)

除了重新定义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 - 对象。它提供了一些方法,可以深入了解检查员的内部情况。

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

只需在console.log (res)之后用return res替换else路径中的return语句。然后你最终得到以下内容。

enter image description here

编辑结束


这是Google修正的先前版本。因此不再是一种可能的方式。

其中一个是挂钩Function.prototype.call

Chrome评估输入的表达式call其评估函数InjectedScriptHostthisArg

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

鉴于此,您可以倾听thisArg call evaluate并获取对第一个参数的引用(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);  
        }
    };
}

你可以,例如抛出错误,评价被拒绝。

enter image description here

<子> 这是一个example,其中输入的表达式在传递给evaluate函数之前传递给CoffeeScript编译器。

答案 4 :(得分:22)

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以引发安全错误。

答案 5 :(得分:21)

这实际上是可能的,因为Facebook能够做到这一点。 好吧,不是实际的Web开发人员工具,而是在控制台中执行Javascript。

请参阅:How does Facebook disable the browser's integrated Developer Tools?

这真的不会做太多,因为还有其他方法可以绕过这种类型的客户端安全性。

当你说它是客户端时,它发生在服务器的控制之外,因此你无能为力。如果你问为什么Facebook仍然这样做,这不是为了安全,而是为了保护那些不知道javascript的普通用户运行代码(他们不知道如何阅读)进入控制台。对于那些承诺自动提供服务或其他Facebook功能机器人的网站来说,这很常见,因为在大多数情况下,它们会为您提供在控制台中运行的javascript片段。

如果你的用户数量不如Facebook,那么我认为没有必要去做Facebook正在做的事情。

即使您在控制台中禁用了Javascript,仍然可以通过地址栏运行javascript。

enter image description here

enter image description here

如果浏览器在地址栏中停用了javascript,(当您将代码粘贴到Google Chrome中的地址栏时,它会删除短语'javascript:')将javascript粘贴到其中一个链接中仍然可以通过inspect元素。< / p>

检查主播:

enter image description here

将代码粘贴到href:

enter image description here

enter image description here

enter image description here

底线是服务器端验证,安全应该是第一个,然后是客户端验证。

答案 6 :(得分:7)

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

答案 7 :(得分:6)

我的简单方法,但它可以帮助进一步改变这个主题。 列出所有方法并将其更改为无用。

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

答案 8 :(得分:3)

devtools内部将名为getCompletions的IIFE注入到页面中,当在Devtools控制台中按下键时会调用该IIFE。

看看source of that function,它使用了一些可以覆盖的全局函数。

通过使用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);
});

答案 9 :(得分:2)

一个简单的解决方案!

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

答案 10 :(得分:0)

我会遵循以下方式:

Object.defineProperty(window, 'console', {
  get: function() {

  },
  set: function() {

  }
});

答案 11 :(得分:0)

Facebook dont do this!!

大家好,请注意,Facebook 不要这样做
这只是一种试图吓唬客户的虚假策略。

默认情况下,此代码在页面加载时写入控制台,无论您是否打开控制台,如果您打开,则您会看到红色的“停止”字样... :)

他们只是重写控制台功能,你可以打开控制台并重写所有这些:|
因为警报功能或所有其他 js 功能仍然可以正常工作。

您可以通过以下代码检测none-separate 开发者工具已打开:

function isConsoleOpen(){
if((window.outerWidth - window.innerWidth)>100){return true;}
if((navigator.platform=="Win32")&&(navigator.userAgent.indexOf("Windows ")==-1)){return true;}else{return false;}}

用于单独的开发者工具打开检测
您必须有一个在 window.blur 上处于活动状态的策略,并检查是否调用了任何函数(除了每隔一段时间从服务器加载数据的函数......)然后算法可以检测到客户端正在单独的开发工具中工作。

简单的解决方案: 将此代码添加到您的主要 js 函数中:

if (document.hasFocus()) {...}

硬性解决方案:
add extra code snippet to all js functions of ur code

答案 12 :(得分:-1)

这不是弱代码无人看管的安全措施。在实施此策略之前,始终要获得弱代码的永久解决方案并正确保护您的网站

根据我的知识,迄今为止最好的工具是添加多个javascript文件,只需通过刷新或替换内容就可以将页面的完整性恢复正常。禁用此开发人员工具不是最好的主意,因为绕过问题始终存在问题,因为代码是浏览器的一部分而不是服务器呈现,因此可能会被破解。

如果您js file one检查重要元素的<element>更改,js file twojs file three检查此文件是否存在于每个期间,您将在页面上进行完整的完整性恢复在此期间内。

让我们举一个4个文件的例子,告诉你我的意思。

<强>的index.html

   <!DOCTYPE html>
   <html>
   <head id="mainhead">
   <script src="ks.js" id="ksjs"></script>
   <script src="mainfile.js" id="mainjs"></script>
   <link rel="stylesheet" href="style.css" id="style">
   <meta id="meta1" name="description" content="Proper mitigation against script kiddies via Javascript" >
   </head>
   <body>
   <h1 id="heading" name="dontdel" value="2">Delete this from console and it will refresh. If you change the name attribute in this it will also refresh. This is mitigating an attack on attribute change via console to exploit vulnerabilities. You can even try and change the value attribute from 2 to anything you like. If This script says it is 2 it should be 2 or it will refresh. </h1>
   <h3>Deleting this wont refresh the page due to it having no integrity check on it</h3>

   <p>You can also add this type of error checking on meta tags and add one script out of the head tag to check for changes in the head tag. You can add many js files to ensure an attacker cannot delete all in the second it takes to refresh. Be creative and make this your own as your website needs it. 
   </p>

   <p>This is not the end of it since we can still enter any tag to load anything from everywhere (Dependent on headers etc) but we want to prevent the important ones like an override in meta tags that load headers. The console is designed to edit html but that could add potential html that is dangerous. You should not be able to enter any meta tags into this document unless it is as specified by the ks.js file as permissable. <br>This is not only possible with meta tags but you can do this for important tags like input and script. This is not a replacement for headers!!! Add your headers aswell and protect them with this method.</p>
   </body>
   <script src="ps.js" id="psjs"></script>
   </html>

<强> mainfile.js

   setInterval(function() {
   // check for existence of other scripts. This part will go in all other files to check for this file aswell. 
   var ksExists = document.getElementById("ksjs"); 
   if(ksExists) {
   }else{ location.reload();};

   var psExists = document.getElementById("psjs");
   if(psExists) {
   }else{ location.reload();};

   var styleExists = document.getElementById("style");
   if(styleExists) {
   }else{ location.reload();};


   }, 1 * 1000); // 1 * 1000 milsec

<强> ps.js

   /*This script checks if mainjs exists as an element. If main js is not existent as an id in the html file reload!You can add this to all js files to ensure that your page integrity is perfect every second. If the page integrity is bad it reloads the page automatically and the process is restarted. This will blind an attacker as he has one second to disable every javascript file in your system which is impossible.

   */

   setInterval(function() {
   // check for existence of other scripts. This part will go in all other files to check for this file aswell. 
   var mainExists = document.getElementById("mainjs"); 
   if(mainExists) {
   }else{ location.reload();};

   //check that heading with id exists and name tag is dontdel.
   var headingExists = document.getElementById("heading"); 
   if(headingExists) {
   }else{ location.reload();};
   var integrityHeading = headingExists.getAttribute('name');
   if(integrityHeading == 'dontdel') {
   }else{ location.reload();};
   var integrity2Heading = headingExists.getAttribute('value');
   if(integrity2Heading == '2') {
   }else{ location.reload();};
   //check that all meta tags stay there
   var meta1Exists = document.getElementById("meta1"); 
   if(meta1Exists) {
   }else{ location.reload();};

   var headExists = document.getElementById("mainhead"); 
   if(headExists) {
   }else{ location.reload();};

   }, 1 * 1000); // 1 * 1000 milsec

<强> ks.js

   /*This script checks if mainjs exists as an element. If main js is not existent as an id in the html file reload! You can add this to all js files to ensure that your page integrity is perfect every second. If the page integrity is bad it reloads the page automatically and the process is restarted. This will blind an attacker as he has one second to disable every javascript file in your system which is impossible.

   */

   setInterval(function() {
   // check for existence of other scripts. This part will go in all other files to check for this file aswell. 
   var mainExists = document.getElementById("mainjs"); 
   if(mainExists) {
   }else{ location.reload();};
   //Check meta tag 1 for content changes. meta1 will always be 0. This you do for each meta on the page to ensure content credibility. No one will change a meta and get away with it. Addition of a meta in spot 10, say a meta after the id="meta10" should also be covered as below.
   var x = document.getElementsByTagName("meta")[0];
   var p = x.getAttribute("name");
   var s = x.getAttribute("content");
   if (p != 'description') {
   location.reload();
   }
   if ( s != 'Proper mitigation against script kiddies via Javascript') {
   location.reload();
   }
   // This will prevent a meta tag after this meta tag @ id="meta1". This prevents new meta tags from being added to your pages. This can be used for scripts or any tag you feel is needed to do integrity check on like inputs and scripts. (Yet again. It is not a replacement for headers to be added. Add your headers aswell!)
   var lastMeta = document.getElementsByTagName("meta")[1];
   if (lastMeta) {
   location.reload();
   }
   }, 1 * 1000); // 1 * 1000 milsec

<强>的style.css

现在这只是为了表明它适用于所有文件和标签以及

   #heading {
   background-color:red;
   }

如果您将所有这些文件放在一起并构建示例,您将看到此度量的功能。如果您在索引文件中的所有重要元素上正确实现它,这将阻止一些不可预测的注入,尤其是在使用PHP时。

为什么我选择重新加载而不是每个属性更改回正常值是因为某些攻击者可能已经配置并准备好网站的另一部分并且它减少了代码量。重新加载将删除所有攻击者的辛苦工作,他可能会更容易在某个地方玩。

另一个注意事项:这可能会成为很多代码,因此请保持清洁,并确保将定义添加到它们所属的位置,以便将来轻松编辑。同时将秒数设置为首选金额,大页面上的1秒间隔可能会对访问者可能使用的旧计算机产生严重影响