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

所以显然是因为最近的骗局,开发者工具被人们利用来发布垃圾邮件,甚至用来“黑客”账户。Facebook已经阻止了开发者工具,我甚至不能使用控制台。

在此处输入图像描述

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

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

这怎么可能?

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

在此处输入图像描述

293905 次浏览

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

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

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

编辑思考@陈志立有正确的想法:从控制台禁用JavaScript执行"::: KSpace:::

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

只是要清楚:试图阻止黑客客户端一般是坏主意;这是为了防止特定社会工程攻击

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

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

Chrome封装所有控制台代码

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

…所以网站重新定义了console._commandLineAPI来抛出:

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

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


结语:Chrome团队认为击败来自用户端JS的控制台是bug和解决了这个问题,使该技术无效。之后,在保护用户免受自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);

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

参考文献:

除了重新定义console._commandLineAPI之外,还有其他一些方法可以在WebKit浏览器上闯入InjecttedScriptHost,以防止或更改对输入开发人员控制台的表达式的评估。

编辑:

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],您会期望以下输出:

输入图片描述

在monkeypatchInjectedScript._evaluateAndWrap评估相同的表达式后,给出以下输出:

在此处输入图片描述

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

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

此外,看一下is-#1-对象。它提供了一些方法来玩并深入了解检查器的内部。

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

只需用console.log (res)return res后面的console.log (res)来替换其他路径中的返回语句。然后你会得到以下内容。

输入图片描述

编辑结束


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

其中一个正在连接Function.prototype.call

Chrome计算输入的表达式,方法是calling它的val函数,InjectedScriptHostthisArg

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

鉴于此,您可以侦听call中的thisArgevaluate并获取对第一个参数的引用(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 ISHish = arguments[0];ish.evaluate = function (e) { //Redefine the evaluation behaviourthrow new Error ('Rejected evaluation of: \n\'' + e.split ('\n').slice(1,-1).join ("\n") + '\'');};Function.prototype.call = _call; //Reset the Function.prototype.callreturn _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,(当您将代码粘贴到GoogleChrome中的地址栏时,它会删除短语“javascript:”)通过检查元素将javascript粘贴到其中一个链接中仍然是可能的。

检查锚点:

在此处输入图片描述

在href中粘贴代码:

在此处输入图片描述

在此处输入图片描述

在此处输入图片描述

底线是服务器端验证和安全性应该是第一位的,然后才是客户端。

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

  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 casewindow.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 toolswindow.dispatchEvent(new Event('devtools-opened'));}else{window.dispatchEvent(new Event('devtools-closed'));}setTimeout(verifyConsole, 100);}verifyConsole();})();

Chrome改变了很多,因为时代facebook可以禁用控制台…

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

这不是让弱代码无人值守的安全措施。在实施此策略之前,请始终为弱代码提供永久解决方案并妥善保护您的网站

据我所知,到目前为止最好的工具是添加多个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 therevar 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秒间隔可能会对访问者可能使用的旧计算机产生巨大影响

一个简单的解决方案!

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

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

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

通过使用#0构造函数,可以获取调用堆栈,当Devols调用时,它将包括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);});

我会沿着这条路走下去:

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

在Firefox中它不这样做,因为Firefox是一个开发人员浏览器,我想是因为命令WEBGL_debug_renderer_info is deprecated in Firefox and will be removed. Please use RENDERER和错误Referrer Policy: Less restricted policies, including ‘no-referrer-when-downgrade’, ‘origin-when-cross-origin’ and ‘unsafe-url’, will be ignored soon for the cross-site request: https://static.xx.fbcdn.net/rsrc.php/v3/yS/r/XDDAHSZfaR6.js?_nc_x=Ij3Wp8lg5Kz

这里有一个简单的方法:window.console = function () {}