app.get("/files", (req, res) => {
const file = path.join(__dirname, "uploads", req.query.name);
res.sendFile(file);
});
This is path traversal. A request like /files?name=../.env or ?name=../../../etc/passwd makes path.join climb out of uploads. The server then sends any file it can read: secrets, source code or system files. Encoded forms like %2e%2e%2f don't help either, because Express decodes the query string before your code sees it.
There are two more problems:
req.query.namecan be an array, as in?name=a&name=b. Thenpath.jointhrows an error.- Nothing checks that this user may download this file. That is an IDOR (Insecure Direct Object Reference) bug.
Fixes, best first:
- Don't use file names from the user. Store files under random ids. Look up the record in the database, check that the user owns it, and then send the stored path.
- Let Express check the folder:
res.sendFile(name, { root: uploadsDir })refuses paths that try to climb out ofroot. - Or check it yourself: resolve the full path and confirm it starts with the uploads folder plus
path.sep. - Validate the input: require a string and allow only safe characters.
- A
../in the name lets the user read files outside the uploads folder. - URL encoding doesn't help, because Express decodes the query first.
- The best fix is random file ids plus a database lookup with an ownership check.
res.sendFilewith therootoption, or a resolve-and-prefix check, keeps paths inside the folder.- Check that the input is a string, because query values can be arrays.