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

How does Facebook disable the browser's integrated Developer Tools?

所以很明显,由于最近的骗局,开发人员工具被人们用来发布垃圾邮件,甚至被用来"黑客"帐户。Facebook已经屏蔽了开发者工具,我甚至不能使用控制台。

Enter image description here

他们是怎么做到的?一个栈溢出帖子声称这是不可能的,但Facebook证明了他们的错误。

只需进入Facebook,打开开发者工具,在控制台中输入一个字符,就会弹出这个警告。不管你放什么东西,都不会被处决。

这怎么可能?

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

氧化镁


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

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

如果你最终进入了测试组,对此很恼火,对不起。我试图使旧的选择退出页面(现在是帮助页面)尽可能简单,同时仍然足够可怕,以阻止至少一些受害者。

实际的代码与@joeldixon66的链接非常相似;我们的代码有点复杂,因为没有好的理由。

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

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

…因此,该站点重新定义console._commandLineAPI来抛出:

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

这还不够(试试看!)但这就是主要技巧。

尾声:Chrome团队认为从用户端JS击败控制台是一个bug,并解决了这个问题,使这种技术无效。之后,添加了额外的保护以保护用户不受自XSS的影响。


我使用Chrome开发者工具找到了Facebook的控制台buster脚本。以下是为提高可读性所做的微小更改的脚本。我去掉了我无法理解的部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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);

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

参考文献:

  • 对象.defineproperty
  • Object.GetOwnPropertyDescriptor
  • chrome的console.log函数(有关格式化输出的提示)


我不能让它在任何页面上触发它。更强大的版本可以做到:

1
2
3
4
5
6
7
8
window.console.log = function(){
    console.error('The developer console is temp...');
    window.console.log = function() {
        return false;
    }
}

console.log('test');

设置输出样式:javascript控制台中的颜色

edit thinking@joeldixon66的想法是正确的:从控制台禁用javascript执行«;::kspace:::


此外redefining console._commandLineAPI,有一些其他的方式来打破在一injectedscripthost WebKit的浏览器,以防止或年龄的评价表达进入到开发者的控制台。

编辑:

这在过去已经固定的铬释放。它必须在2015年前的二月,我创建的GIST那时

所以这是另一个房间。这个时间我们在A级以上,钩,而直接进入InjectedScriptInjectedScriptHost作为反对到之前的版本。

这是很好的时候,你可以直接InjectedScript._evaluateAndWrap猴子补丁而有依靠,给你更多的InjectedScriptHost.evaluate为细晶控制过什么应该发生的。

另一个很有趣的事情是,这是我们可以拦截的内部结果的表达是在被回报,而不是在用户的正常行为。

这里是代码,这是不准确的,当一个用户返回的内部evaluates东西在控制台。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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);

这是一位",但我想我把它的一些评论。

所以通常,如果一个用户,例如,evaluates [1,2,3,4]你期望以下输出:

enter image description here

后评价monkeypatching InjectedScript._evaluateAndWrap非常相同的表达,给下面的输出:

enter image description here

当你看到小左箭头,指示输出,有一点时间,但这对我们得到的对象。在结果的表达,是更为[1,2,3,4]数组对象的所有属性描述它。

好的,我想这是个错误的表达,包括那些产生。它是相当有趣的。

此外,采取一看is- InjectedScriptHost对象。它提供了一些方法去玩和得到一点洞察到内部的督察。

当然,你可以拦截所有的信息和结果返回到安静的原用户。

只是在else语句替换返回路径由一console.log (res)a return res以下。然后你最终有以下。

enter image description here

最终的编辑

这是之前的版本,这是固定的,通过谷歌。因此不可能的方式了。

<一>是一Function.prototype.callhooking

这是由铬evaluates eval函数和其表达的calling InjectedScriptHostAS thisArg></s

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

在这,你可以在列表的thisArgof callevaluateget a参考指南和第一参数(InjectedScriptHost)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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:
\''
+ e.split ('
'
).slice(1,-1).join ("
"
) + '\'');
            };
            Function.prototype.call = _call; //Reset the Function.prototype.call
            return _call.apply(this, arguments);  
        }
    };
}

例如,你可以把错误,这不是什么评价。

enter image description here

<副>这里是一个例子是在传递一个coffeescript表达得到它之前传递到evaluate编译器的功能。

子></


所以在Netflix实现特征

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(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。

enter image description here

氧化镁

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

检查锚:

氧化镁

在Href中粘贴代码:

氧化镁

氧化镁

enter image description here

底线是服务器端验证,安全性应该先做,然后做客户端验证。


自从Facebook可以禁用控制台以来,Chrome改变了很多…

截至2017年3月,这已经不起作用了。

您最好禁用一些控制台功能,例如:

1
2
3
4
5
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(){};
}


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

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

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

从这个函数的源代码来看,它使用了一些可以被覆盖的全局函数。

通过使用Error构造函数,可以获得调用堆栈,当devtools调用时,调用堆栈将包括getCompletions

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
setInterval(()=>console.clear(),1500);


我会沿着这条路走:

1
2
3
4
5
6
7
8
Object.defineProperty(window, 'console', {
  get: function() {

  },
  set: function() {

  }
});


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

据我所知,目前最好的工具是添加多个javascript文件,通过刷新或替换内容,将页面的完整性简单地更改回正常状态。禁用这个开发人员工具不是最好的主意,因为绕过总是有问题的,因为代码是浏览器的一部分,而不是服务器渲染,因此它可能被破解。

如果您有js file one检查重要元素的更改,以及js file twojs file three检查此文件是否按时间段存在,您将在该时间段内在页面上恢复完整性。

让我们以4个文件为例,向您展示我的意思。

index.html文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
   <!DOCTYPE html>
   <html>
   <head id="mainhead">
   <script src="ks.js" id="ksjs">
   <script src="mainfile.js" id="mainjs">
   <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.
   Deleting this wont refresh the page due to it having no integrity check on it

   <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. 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">
   </html>

主文件.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
   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

PSS JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   /*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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
   /*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

样式表

现在,这只是为了显示它在所有文件和标签上也能工作。

1
2
3
   #heading {
   background-color:red;
   }

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

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

另一个注意事项:这可能会成为许多代码,因此请保持代码的整洁,并确保向它们所属的位置添加定义,以便于将来编辑。另外,将秒数设置为您的首选值,因为大页面上的1秒间隔可能会对您的访问者可能使用的旧计算机产生巨大影响。