CodePark/ questions / Deserialization, XML, archives & paths
START PRACTICINGPRACTICE
Backend question · Auth & security › Deserialization, XML, archives & paths

What's wrong with this download endpoint, and how do you fix it?

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.name can be an array, as in ?name=a&name=b. Then path.join throws 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 of root.
  • 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.sendFile with the root option, or a resolve-and-prefix check, keeps paths inside the folder.
  • Check that the input is a string, because query values can be arrays.