利用javascript压缩工具留下后门

来源:互联网 时间:1970-01-01

在POC||GTFO 0×08的文章” Deniable Backdoors Using Compiler Bugs”中,作者提到了利用CLANG编译器本身的bug来生成一个有后门的sudo,使其允许任何用户获取root权限。这种技术相当猥琐,毕竟没人能通过审查源码就证明他们对sudo的修改是一个后门;相反,这个权限提升后门是由特定版本的CLANG在编译的时候插入进去的。

这不禁让我思考,能否在javascript中使用同样的后门技术。JS几乎处处可见其身影(从浏览器,到服务器,到arduino,到机器人,甚至某一天还可能在汽车上见到它的踪迹)。但是javascript是解释型语言,不是编译型语言。然而,对js进行削减和优化,以减小文件大小,提升性能,这种做法很常见。或许通过使用js压缩工具,我们就能找到插入后门的机会。

Part I: 寻找压缩工具中的bug

流行的js压缩工具真的有能导致安全问题的bug?

搜寻了10分钟左右,我在Uglifyijs中就发现了一个这样的bug,UglfyJs是一个流行的js压缩工具,在JQuery中使用了它来建立脚本,同时Internet上大约70%的顶级网站都使用了JQuery。该bug在2.4.24 release版中进行了修复,bug本身原因很简单,但却不是很明显。那么让我们来看看它。

UglifyJS做了一系列事情来精简文件大小。其中默认的压缩标记会对如下的表达式进行压缩:

!a && !b && !c && !d

该表达式的长度是20字符,如果我们使用德摩根定律,则可以将其改写为:

!(a || b || c || d)

而这个新的表达式只有19个字符。看起来还不错,问题在于如果其中的任何一个子表达式有非布尔型的返回值,德摩根定律并不能像预期的那样工作。比如:

!false && 1

会返回数字1.而另一方面

!(false || !1)

仅仅返回true.

因此,如果我们可以欺骗js压缩工具错误的使用德摩根定律,那么我们可以使程序在压缩前后表现不一样。结果显示,让UglifyJS 2.4.23这样做并不难,因为如果使用的摩根定律后的表达式比原表达式更短,它就会使用这个表达式替代换来的表达式(UglifyJs 2.4.24对次已进行了修补,会确保在重写表达式前子表达式的值是布尔型的)。

Part II: 在某些身份验证代码中构建后门

很好,我们已经找到了期望的js压缩工具的bug.下面来对其进行利用。

假设你正为某个公司工作,希望在其Node.js网站有意留下后门。而你被要求写一些服务器端的脚本来验证用户的认证token是否过期。首先,确保Node包使用[email protected],(该版本有我们上面提到的bug).

接着你写了token验证函数,插入了一堆看起来是配置和用户认证的检查操作,来迫使压缩工具来压缩的长的非布尔表达式。

function isTokenValid(user) {var timeLeft =!!config && // config object exists!!user.token && // user object has a token!user.token.invalidated && // token is not explicitly invalidated!config.uninitialized && // config is initialized!config.ignoreTimestamps && // don't ignore timestampsgetTimeLeft(user.token.expiry); // > 0 if expiration is in the future// The token must not be expiredreturn timeLeft > 0;}function getTimeLeft(expiry) { return expiry - getSystemTime();}

使用uglifyjs –c 运行上面的代码片段,输出结果如下:

function isTokenValid(user){var timeLeft=!(!config||!user.token||user.token.invalidated||config.uninitialized||config.ignoreTimestamps||!getTimeLeft(user.token.expiry));return timeLeft>0}function getTimeLeft(expiry){return expiry-getSystemTime()}

在原始形式中,如果config 和user的检查通过了,在token已经过期的时候timeLeft是一个负数.在压缩后了的形式中,timeLeft必须是布尔型的(因为”!”会进行强制类型转换为布尔型)。实际上,如果在config和user检查通过的时候,timeLeft会一直为true,除非getTimeLet的返回值刚好为0.由于在javascript中true>0(由于类型强制转换),过期的认证token将会一直有效。

Part III: 修改jQuery

下面,让我们利用这个js压缩工具的bug来对jQuery本身进行一些修改,使其留下后门。我们将使用写本文时jquery的稳定发行版,jQuery 1.11.3.

JQuery 1.11.3使用gurney-contrib-gulify0.3.2来进行js的压缩,这反过来又依赖于uglify-js-2.4.0.因此[email protected]满足这个依赖,我们可以手工改动grunt-contrib-uglify中的package.json文件来强制它使用这个版本的uglify-js。

在jQuery中有多处使用德摩根定律进行表达式重写优化。这其中没有一处能导致出现bug,那我们就自己加吧。

Backdoor Patch #1:

首先,我们在jQuery的.html()方法加一个潜在的后门,这个改动看起来有些怪异,且显得多余,但我们可以让其他人相信,它并不会改变原来的操作。实际上,压缩前,单元测试通过。

在使用[email protected]进行压缩后,jQuery的.html()方法会设置innerHtml为true而不是给定的值,因此一系列测试会失败。

然而,jQuery的维护者可能正使用打了修改了的uglifyjs。事实上,[email protected]通过了测试,因此,这个修改看起来也不会太可疑。

现在我们运行grunt来建立进行了这一修改的jQuery,同时使用简单的代码来触发这个漏洞。

<html><script src="../dist/jquery.min.js"></script><button>click me to see if this site is safe</button><script>$('button').click(function(e) {$('#result').html('<b>false!!</b>');});</script><div id='result'></div></html>

下面是使用js未压缩的jQuery,点击按钮后的运行结果:

和预期的一样,用户被警告站点不安全。这看起来觉得讽刺,因为它并没有使用我们的js压缩工具触发的后门。下面是使用我们的用了js压缩工具的jQuery的情况:

现在用户会认为这个站点是安全的,即使是站点的作者试图对提醒访问者站点不安全。

Backdoor Patch #2:

第一个后门可能过于简单而很容易被检测到,毕竟任何使用它的人都可能会注意到一堆HTML都被设置为了字符串”true”而不是他们想设置的HTML.因此,我们的第二个后门仅仅在特殊情形下才会触发。

我们主要修改了jQuery.event.remove方法(在.off()方法中使用),因此调用了 特殊事件移除钩子 的代码在压缩后,就不会被调用到。(由于切割通常是boolen型的,它的长度通常是没有定义的,不是大于0的)。这不一定会改变网站的行为,除非开发者定义了一个这样的钩子.

假设我们需要留下后门的网站的html代码如下:

<html><script src="../dist/jquery.min.js"></script><button>click me to see if special event handlers are called!</button><div>FAIL</div><script>// Add a special event hook for onclick removaljQuery.event.special.click.remove = function(handleObj) {$('div').text('SUCCESS');};$('button').click(function myHandler(e) {// Trigger the special event hook$('button').off('click');});</script></html>

如果我们运行没有使用压缩的jQuery的jQuery,事件移除钩子如预期的那样被正常调用。

但如果我们使用压缩的版本,事件移除钩子则不会被调用。

显然,这并不是什么好消息,如果事件移除钩子做一些安全相关的功能,比如在传递用户认证token到它的时候,检查orgin是不是在白名单中。

总结

我展示的后门例子是人为添加的,但是事实是他们是可以存在的,这可能让js开发者担心了,尽管js 压缩工具并不像C++那样复杂,或者说重要,但他们支撑了很多在web上运行的代码。

好的一点是uglifyjs已经为已知的bug添加了测试用例,但我任然建议那些使用了非正常认证的压缩工具的人小心。除非不得不这样做,否则不要压缩服务端的代码。并确信在浏览器上运行了测试用例,扫描了使用了压缩工具后得到的代码。

原作者的poc: https://github.com/diracdeltas/jquery

* 参考来源 zyan.scripts.mit.edu ,FB特约作者living翻译,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)



相关阅读:
Top