Exposing too much information is a common problem in web applications. It happens because the server is misconfigured or because there’s no mechanism Chapter 13. Protect Your Data
•
176to stop path traversal. Anyone can access materials even if they’re not authorized.
Oversharing was a common configuration problem in LAMP stacks because Apache had a default directory-sharing configuration that tried to disallow files of a certain type. Fortunately for us, Node.js doesn’t thrust a sharing configuration onto developers by default. But it’s easy to introduce this error yourself.
Can you spot the problem in the following code?
chp-13-sensitive-data/oversharing/app.js 'use strict';
var express = require('express');
var app = express();
app.use(express.static(__dirname));
app.get('/', function(req, res){
res.send('<script src="/public/main.js"></script>');
});
app.listen(3000);
Consider everything we’ve discussed so far, and you’ll see that the issue lies with this line:
app.use(express.static(__dirname));
The problem is that it gives public file access to the whole application direc-tory, which means that anyone can request files in the directory. Visiting /app.js would show you the application file, for example.
This isn’t a big issue for this example because the file itself doesn’t reveal any secrets. But mistakes like this can give attackers access to configuration files (with passwords) or code files, where they can look for flaws. It’s an easy mistake to make that has serious consequences.
In order to avoid this issue, place public files in a separate folder and share only that folder. I also suggest using a specific path like this:
app.use('/public', express.static(__dirname + '/public'));
Sometimes you want to use proxies in front of your Node application—like the recommended use of nginx, lighttpd, or H2O for file serving.1
1. http://nginx.org, http://www.lighttpd.net, and https://h2o.examp1e.net/index.html.
Secure the Data Stored Within Your Application
•
177In those configurations you have to make sure that the proxy server itself is properly configured and doesn’t allow access to files it’s not supposed to.
Oversharing isn’t the only thing to worry about. Let’s follow up on our static file-serving problems with the path traversal attack. This is an attack vector that tries to break out of the intended public folder and access files that are not supposed to be accessible by using specially crafted strings.
This attack vector used to be so common that servers and libraries like express.static() added their own defenses. If you don’t use the libraries, you’ll need to roll your own protection. You must understand what’s happening before you can set up a robust protection mechanism. Let’s take a quick look.
Here’s an app that serves files by building the path from query parameters:
chp-13-sensitive-data/traversal/app.js 'use strict';
var express = require('express');
var fs = require('fs');
var app = express();
//Construct path
function getPath(filename) {
return __dirname + '/public/' + filename;
}
app.get('/', function (req, res) { if(!req.query.file) {
res.sendStatus(404);
return;
}
var filePath = getPath(req.query.file);
var stream = fs.createReadStream(filePath);
//Handle errors
stream.on('error', function (err) {
var status = err.code === 'ENOENT' ? 404 : 500;
res.sendStatus(status);
});
stream.pipe(res);
});
app.listen(3000);
You could then ask for a file like /?file=data.json and the server would send you the file. This might look okay, because you’re constructing the path with the /public folder inside. However, attackers can use the file system’s upward Chapter 13. Protect Your Data
•
178traversal property, ask for /?file=../app.js, and get the contents of app.js. The path-traversal possibilities don’t stop there, since you could access any file the server process can, including system password files and keys.
Fortunately in Node.js it’s easy to set up a robust defense against path traversal. You simply need to construct the absolute path and check that it starts with the absolute path of your expected public folders.
Let’s modify the path construction function to add a validation step to check if the path is what you expect it to be:
chp-13-sensitive-data/traversal/app-fixed.js var path = require('path');
var root = path.join(__dirname, '/public');
//Construct absolute path function getPath(filename) {
return path.join(root, filename);
}
//Validate path
function validate(filePath) {
// Expect the filepath to start with // our public root path
return filePath.indexOf(root) === 0;
}
chp-13-sensitive-data/traversal/app-fixed.js
var filePath = getPath(req.query.file);
if(!validate(filePath)) { res.sendStatus(404);
return;
}
Not Only Read
A common mistake is expecting path-traversal attacks only when requesting files from the server. That’s far from true. In fact, it’s often useful for attackers to use path-traversal attacks when uploading files to the server, because people tend to forget path traversal in these situations.
Write path traversal allows attackers to overwrite server files and even the /etc/passwd file to gain access to the server, even if the server is running with hightened security privileges. So it’s important to be vigilant with path checks when using user input in file path construction.
Secure the Data Stored Within Your Application
•
179Limiting public file access to specific controlled folders or files and always constructing and validating absolute paths before actual file access allows you to make sure that you’re safe against being overly open with your data.