flawopen.com/Path Traversal/JavaScript
A file download script is told: 'Read a file inside /uploads/'. If a user asks for '../../package.json', the computer steps back two folders, out of /uploads/ and into the root application folder, reading your source code and API keys.
In Node.js, path.join(__dirname, 'public', req.query.file) normalizes relative dots but does NOT constrain the result to the public directory. If the query string contains ../../../../etc/passwd, the resulting path points outside the webroot.
In 2024, path traversal vulnerabilities in multiple Node.js static server packages and Git CLI integrations allowed remote unauthenticated attackers to exfiltrate .env credentials and private SSH keys.
// VULNERABLE: path.join allows directory breakout
const express = require('express');
const path = require('path');
const fs = require('fs');
const app = express();
const PUBLIC_DIR = path.join(__dirname, 'public');
app.get('/download', (req, res) => {
// Attacker input: "../../etc/passwd"
const targetFile = path.join(PUBLIC_DIR, req.query.file);
// Directly pipes arbitrary system file to response!
res.sendFile(targetFile);
});
// HARDENED: Canonicalize path and assert prefix with directory separator
const express = require('express');
const path = require('path');
const fs = require('fs');
const app = express();
const PUBLIC_DIR = path.resolve(__dirname, 'public');
app.get('/download', (req, res) => {
const userInput = req.query.file;
if (!userInput || typeof userInput !== 'string') {
return res.status(400).send('Invalid file parameter');
}
// 1. Resolve to absolute path
const safePath = path.resolve(PUBLIC_DIR, userInput);
// 2. Strict boundary check: must start with base folder + separator
if (!safePath.startsWith(PUBLIC_DIR + path.sep)) {
return res.status(403).send('Forbidden: Path Traversal detected');
}
// 3. Verify file exists and is a regular file
fs.stat(safePath, (err, stats) => {
if (err || !stats.isFile()) return res.status(404).send('File not found');
res.sendFile(safePath);
});
});
path.resolve() resolves all .. sequences to an absolute canonical string. Checking that safePath.startsWith(PUBLIC_DIR + path.sep) guarantees the target file is strictly inside the folder and prevents sibling directory confusion.
Checking safePath.startsWith(PUBLIC_DIR) allows access to /var/www/public_secret when PUBLIC_DIR is /var/www/public. Always append path.sep.
Passing { root: PUBLIC_DIR } in Express res.sendFile automatically restricts traversal, but using fs.readFile manually does not.
path.normalize() only resolves relative dots; it does not check if the resolved path stepped above the base directory.
grep -rn "path\.join.*req\." --include="*.js" .
# ESLint security rule: npm i -D eslint-plugin-security
path.resolve()safePath.startsWith(baseDir + path.sep)typeof req.query.file === 'string' to prevent type confusion / array injectionexpress.static() with dotfiles: 'ignore' for static file servingNo. Attackers send raw or doubly-encoded traversal strings that web frameworks automatically decode before passing to route handlers.