Symlink Attack in Express with Bearer Tokens
Symlink Attack in Express with Bearer Tokens β how this specific combination creates or exposes the vulnerability
A symlink attack in an Express API that uses Bearer tokens occurs when an attacker can trick the server into reading or overwriting files outside the intended directory by leveraging symbolic links. This typically arises when file paths are derived from user-supplied input and the server uses filesystem operations without proper validation. Bearer tokens are often handled as opaque strings stored in databases or logs; if token values or references to token locations are used to construct filesystem paths, an attacker may supply a malicious path that resolves through a symlink to sensitive files or authentication material.
Consider an Express endpoint that stores uploaded artifacts keyed by a token-derived identifier and uses the token value directly in a filesystem path. If an attacker registers or modifies a token so that the resulting path points to a directory traversal or a symlink on the filesystem, the server may read or overwrite arbitrary files. In a black-box scan, middleBrick tests such unauthenticated endpoints and can surface findings related to Path Traversal and improper access controls that enable symlink-based techniques. Since Bearer tokens often appear in logs and error messages, insecure handling may also lead to token leakage, which compounds the risk by exposing credentials used for authorization.
For example, an endpoint that builds a file path from a token parameter without resolving symlinks or normalizing paths could allow reading /etc/passwd via a crafted symlink placed in a writable directory. The API may return sensitive data or runtime artifacts because the authorization boundary enforced by the token is bypassed at the filesystem layer. This illustrates how authentication via Bearer tokens does not automatically protect file operations; the server must validate and sanitize any path construction and disable symlink resolution when working with user-influenced input.
Bearer Tokens-Specific Remediation in Express β concrete code fixes
Remediation centers on ensuring that Bearer token values and any filesystem interactions are strictly isolated. Do not derive filesystem paths from token strings or include token material in file operations. Use a server-side mapping (e.g., a database) to associate tokens with non-sensitive, normalized identifiers, and store files under a dedicated directory with strict permissions.
Always resolve paths to their realpath and enforce that they remain within an allowed base directory. Avoid following symlinks when serving user-influenced files, and prefer streaming or safe libraries for file delivery. The following Express snippets illustrate secure handling compared to insecure patterns.
| Insecure Pattern | Why Itβs Risky | Secure Alternative |
|---|---|---|
const path = require('path'); app.get('/files/:token', (req, res) => { const filePath = path.join('/data/uploads', req.params.token); res.sendFile(filePath); }); | Uses token directly in path; susceptible to directory traversal and symlink attacks. | const path = require('path'); const baseDir = path.resolve('/data/uploads'); app.get('/files/:token', (req, res) => { const safeName = req.params.token.replace(/[^a-zA-Z0-9_-]/g, '_'); const filePath = path.join(baseDir, safeName); if (!filePath.startsWith(baseDir)) return res.status(403).send('Forbidden'); res.sendFile(filePath); }); |
const express = require('express'); const fs = require('fs'); const path = require('path'); const app = express();
const baseDir = path.resolve('/data/uploads');
// Secure: map token to a safe filename and enforce base directory
app.get('/download/:token', (req, res) => { const token = req.params.token; const safeName = token.replace(/[^a-zA-Z0-9_-]/g, '_'); const filePath = path.join(baseDir, safeName); const realBase = fs.realpathSync(baseDir); const realPath = fs.realpathSync(filePath); if (!realPath.startsWith(realBase)) { return res.status(403).send('Forbidden'); } if (!fs.existsSync(realPath) || !fs.statSync(realPath).isFile()) { return res.status(404).send('Not found'); } res.header('Content-Type', 'application/octet-stream'); res.header('Content-Disposition', 'attachment; filename="' + path.basename(realPath) + '"'); fs.createReadStream(realPath).pipe(res); });
app.listen(3000);
In the secure pattern, token values are never used as raw paths. The code resolves real paths, checks that they remain within the allowed base directory, and avoids symlink following. This approach ensures that even if an attacker can influence token values or place symlinks, the server cannot be tricked into accessing unauthorized files.