T05 · Unauthorized Access and Privilege Escalation
- Location
index.js:97- Finding
Path Traversal in the Video Download Endpoint
- Content
View full analysis
{ const filePath = path.join(__dirname, 'outputs', `${req.params.id}.mp4`); if (fs.existsSync(filePath)) { res.download(filePath); } else { res.status(404).send('Video not found'); } }); ``` ### Technical Analysis The `id` route parameter is inserted directly into a local filesystem path. The application does not verify that the value is a UUID, reject path separators, or confirm that the resolved path remains inside the intended `outputs` directory. Because route parameters are URL-decoded, a value containing encoded traversal sequences and path separators may cause `path.join()` to resolve a path outside `outputs`. The application then checks the attacker-selected path with `fs.existsSync()` and returns it with `res.download()`. The route always appends `.mp4`, so exploitation is limited to accessible files whose resulting path ends with that extension. This restriction reduces scope but does not prevent unauthorized access to media or other files using that extension. ### Attack Path 1. The attacker identifies the unauthenticated `/download/:id.mp4` endpoint. 2. The attacker supplies encoded `..` segments and path separators as the `id` parameter. 3. Express decodes the route parameter. 4. `path.join(__dirname, 'outputs', ...)` normalizes the traversal sequences and may produce a path outside the `outputs` directory. 5. If the resulting `.mp4` file exists and is readable by the Node.js process, `res.download()` returns it to the attacker. ### Impact Assessment A remote unauthenticated attacker may download files outside the intended output directory when: - The target path resolves to an existing file ending in `.mp4`. - The Node.js process has permission to read the target file. The vulnerability vi ...[truncated 225 chars]- Remediation
View remediation
{ if (!UUID_RE.test(req.params.id)) { return res.status(400).send('Invalid video identifier'); } const outputRoot = path.resolve(__dirname, 'outputs'); const filePath = path.resolve(outputRoot, `${req.params.id}.mp4`); if (!filePath.startsWith(`${outputRoot}${path.sep}`)) { return res.status(400).send('Invalid video identifier'); } res.download(filePath, (error) => { if (error && !res.headersSent) { res.status(error.statusCode === 404 ? 404 : 500).send('Unable to download video'); } }); }); ``` 2. Store generated IDs and corresponding server-controlled paths in a database or lookup table rather than deriving arbitrary paths from request parameters. 3. Apply authorization checks so that only the owner of a generated video can download it. 4. Run the service under a dedicated account with read access limited to application-owned directories. 5. Add automated tests using encoded traversal sequences, mixed separators, malformed UUIDs, and nonexistent identifiers. ]]>
