Vulnerability Introduction

This post is a walkthrough of the vulnerability that we discovered that allows execution of arbitrary commands on a system with the NVIDIA GeForce Experience (GFE) prior to version 3.19 installed – CVE-2019-5678. The exploit can be achieved by convincing a victim to visit a crafted web site and make a few key presses. This is possible due to command injection which was discovered in a local “Web Helper” server which GFE launches on startup.

NVIDIA GFE

The NVIDIA website describes GeForce Experience as a way to “capture and share videos, screenshots, and livestreams with friends, and keep your drivers up to date and optimize your game settings.” It is essentially a supplementary application which is installed alongside GeForce products in order to give a user added functionality.

Discovering the Vulnerability

MWR Labs stated in this blog post that GFE starts a local API server which allows control over different aspects of GFE. When you change a setting in the GFE GUI interface, it is likely just making a call to this local API. Knowing this, I thought it may be worthwhile to look into the API to see if there was any interesting functionality. The server that is started by GFE is NodeJS Express and many of the JavaScript source files can be found in “C:\Program Files (x86)\NVIDIA Corporation\NvNode”. In order to make a valid request to the server, a custom HTTP header is required that contains a random token which is generated on startup and stored in “%LOCALAPPDATA%\NVIDIA Corporation\NvNode

odejs.json” along with the random port the service is started on. The file name, nodejs.json, is static and can always be found at this location which makes it trivial to blindly locate this file. The image below shows what the contents of this file look like.

Examining the Authentication Header

I first wanted to find out if it was possible to bypass the authentication header and make valid unauthenticated requests to the API. Taking a look into index.js on line 185, the check of the header is just a comparison to the secret value contained in the file to the custom security header. If this fails then it simply returns a 403 response.

I was unable to find any type of bypass for this as it is fairly straightforward. Something interesting to note, though, is the way CORS is being used. On line 181 the “Access-Control-Allow-Origin” header is always set to “*” on all requests. This means if it was possible to somehow obtain the secret token it would be possible to make valid requests to the server even from a different Origin like an attacker controlled web site. In addition, it would be possible to send the custom security header along with the request as well using an XHR request.

Testing the API

With this in mind, I decided to move on to see if I could do anything with the API if I were to make a successful request to it, such as executing code. Grepping all the source files for “exec“ returned an interesting result in NvAutoDownload.js.

Here you can see the endpoint “/gfeupdate/autoGFEInstall/” takes a POST request and if the Content-Type is set to “text/*” it will eventually insert whatever text is in the body of the request via “req.text” directly into the childProc function and execute it as an operating system command. I tested this out with a normal request to ensure it would execute.

The request above would successfully execute calc.exe. This alone was not much of an issue since an attacker would need to know the secret token contained in a file they could not read. So, the next step was to find a way to potentially read the file containing the secret so this could be exploited.

Exploitation

As pointed out earlier, this attack could be performed via a browser since the CORS policy which was implemented would allow this request to come from any Origin. This attack still required having knowledge of the secret token. The only way around this is if a user could be tricked into uploading the file containing the token. But since the secret token file has a static path and name this could be achieved fairly easily in the browser, which would only require the user to press a couple keys to achieve command injection.

Step-by-Step Walkthrough of Exploitation

In Chrome it is possible to have a key press copy arbitrary text to the clipboard, but in Firefox this step would require a mouse click of some kind. The final exploit for Chrome requires 3 keys to be pressed, “CTRL+V+Enter”. The breakdown of the steps are as follows: “CTRL” – Fills the clipboard with the path to the file containing the secret, “%LOCALAPPDATA%\NVIDIA Corporation\NvNode

odejs.json”. This also opens the file-input windows to select a file to upload. “V” – The contents of the clipboard is inserted is pasted into the file input window. “Enter” – Uploads the nodejs.json file allowing the page to read the contents. A request is then immediately sent to the GFE API via an XHR request using the information from the file to execute a command. All of this working together can be seen below.

Conclusion

Although this does require some user interaction, it is minimal enough that tricking a user into performing the actions which are required to achieve this would be trivial. The real issue here seems to be that the API allows Cross Origin Resource Sharing from any Origin, which means it is possible to perform an XHR request to any of the endpoints through the browser if the secret token were obtained through any method. The command injection issue has been fixed in the latest release of GFE (3.19), so be sure that you are running the latest version. 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. If you do not use GFE, it is a good idea to simply uninstall it from your system to reduce the additional attack surface it introduces. The proof of concept for this write-up can be found in our GitHub repo.

Disclosure Timeline