Find the vulnerability in reading a file by its name from the request
This Node.js handler serves a file whose name comes from the query string. Identify the vulnerability and how an attacker exploits it, then describe the fix.
Constraints:
- assume
query.fileNameis fully attacker-controlled - the intended directory is
./open-media/ - focus on the file-system access, not on transport security
const pathToFile = `./open-media/${query.fileName}`;
const file = fs.readFileSync(pathToFile, 'binary');
res.write(file, 'binary');
Diagnose the cause.
Path traversal — fileName is concatenated into the path unchecked, so ../ sequences climb out of open-media/ and read arbitrary files. Fix: normalize the resolved path and verify it stays inside the base directory, or map requests to an allowlist of filenames.
- ✗Believing that filtering only the file extension stops directory traversal
- ✗Relying on stripping the '../' substring without normalizing the resolved path
- ✗Confusing path traversal with XSS because user input is present
- →Why must you validate the normalized path, not the raw string?
- →Why is an allowlist-of-names approach more reliable than filtering '../'?
The vulnerability
fileName comes from the request and is concatenated into the path with no validation:
const pathToFile = `./open-media/${query.fileName}`;
By passing fileName = ../../etc/passwd, an attacker climbs out of open-media/ up the directory tree and reads any file the process can access — this is path traversal.
The fix
Normalize the resolved path and confirm it stays inside the base directory:
const path = require('path');
const baseDir = path.resolve('./open-media');
const resolved = path.resolve(baseDir, query.fileName);
if (!resolved.startsWith(baseDir + path.sep)) {
res.statusCode = 400;
return res.end('Invalid file name');
}
const file = fs.readFileSync(resolved, 'binary');
res.write(file, 'binary');
⚠️ Validate the normalized path (path.resolve), not the raw string — otherwise ....// and encoded variants slip past a naive ../ filter. Even safer: map the request to an allowlist of known filenames.