HIGH symlink attackexpressbasic auth

Symlink Attack in Express with Basic Auth

Symlink Attack in Express with Basic Auth — how this specific combination creates or exposes the vulnerability

A symlink attack in an Express service that uses HTTP Basic Auth occurs when an attacker can trick the server into reading or overwriting files outside the intended directory through path traversal or symbolic link resolution. Even when endpoints are protected by Basic Auth, the vulnerability exists at the filesystem interaction layer, not the authentication layer. If route handlers construct file paths from user-controlled input (e.g., username, filename parameters, or uploaded file metadata) and pass those paths to filesystem operations without canonicalization or strict validation, an authenticated or unauthented attacker can leverage path traversal sequences like ../../../ or malicious symlinks to escape the intended directory boundary.

Consider an Express route that serves user-specific configuration files under a directory presumed to be user-homed:

const express = require('express');
const fs = require('fs');
const path = require('path');
const app = express();

app.use((req, res, next) => {
  const user = req.headers['x-forwarded-user'] || 'default';
  req.baseDir = path.join(__dirname, 'data', user);
  next();
});

app.get('/config/:filename', (req, res) => {
  const filePath = path.join(req.baseDir, req.params.filename);
  fs.readFile(filePath, (err, data) => {
    if (err) return res.status(404).send('Not found');
    res.send(data);
  });
});

app.listen(3000);

If the req.baseDir is derived from a header and not verified to be canonical, an attacker who knows or guesses a valid Basic Auth account can use filename=../../../etc/passwd to read sensitive files. Symlinks amplify this: if the attacker can place a symlink within the presumed directory that points to a sensitive location, a seemingly safe fs.readFile can disclose or modify files outside the sandbox. Basic Auth does not prevent this because the filesystem access is evaluated after authentication; the route handler’s trust in user-supplied path segments remains the weak link.

The risk is especially relevant when combined with common misconfigurations such as permitting user-controlled filenames in uploads or allowing multipart fields to dictate storage paths. Even with per-user directories, if the application does not resolve paths to their canonical absolute forms and verify they remain within the intended base directory, the attack surface includes both traversal and symlink-induced escapes. This maps to OWASP API Top 10:2023 Broken Object Level Authorization (BOLA) and related Insecure Direct Object References (IDOR), where improper enforcement of access controls on file resources leads to unauthorized data exposure.

Basic Auth-Specific Remediation in Express — concrete code fixes

Remediation focuses on strict path canonicalization, scope confinement, and avoiding user-controlled input in filesystem paths. Do not rely on authentication or authorization alone to protect file system operations. Use the built-in path.resolve and validate that the resolved path begins with the canonical base directory. Prefer serving files through a controlled mapping or a token-based lookup rather than direct filename passthrough.

Secure Express example with Basic Auth and safe path handling:

const express = require('express');
const fs = require('fs');
const path = require('path');
const app = express();

const USERS = {
  alice: { password: 's3cret', home: path.join(__dirname, 'data', 'alice') },
  bob:   { password: 'w0rd',  home: path.join(__dirname, 'data', 'bob') }
};

const basicAuth = (req, res, next) => {
  const auth = req.headers.authorization;
  if (!auth || !auth.startsWith('Basic ')) {
    res.set('WWW-Authenticate', 'Basic realm="API"');
    return res.status(401).send('Authentication required');
  }
  const buf = Buffer.from(auth.slice(6), 'base64');
  const [user, pass] = buf.toString().split(':');
  const entry = USERS[user];
  if (!entry || entry.password !== pass) {
    return res.status(401).send('Invalid credentials');
  }
  req.user = user;
  req.baseDir = entry.home;
  next();
};

app.use(basicAuth);

app.get('/config/:filename', (req, res) => {
  // Explicitly resolve and ensure containment within the user's home
  const requested = path.resolve(req.baseDir, req.params.filename);
  if (!requested.startsWith(path.resolve(req.baseDir))) {
    return res.status(403).send('Forbidden');
  }
  fs.readFile(requested, (err, data) => {
    if (err) return res.status(404).send('Not found');
    res.type('txt').send(data);
  });
});

app.post('/upload', express.raw({ type: '*/*' }), (req, res) => {
  const filename = 'upload_' + Date.now() + '.bin';
  const filePath = path.join(req.baseDir, filename);
  fs.writeFile(filePath, req.body, (err) => {
    if (err) return res.status(500).send('Upload failed');
    res.send({ file: filename });
  });
});

app.listen(3000);

Key practices demonstrated:

  • Validate credentials and derive a trusted baseDir per request rather than building paths from headers.
  • Use path.resolve to eliminate .. and symlink components, then assert the resolved path starts with the canonical base directory.
  • Avoid passing user-controlled filenames directly to filesystem APIs; generate safe names on write and use lookup maps on read.
  • Serve files with an explicit MIME type and avoid eval-like behavior on uploaded content.

These measures reduce the risk of symlink-based path traversal and ensure that even if an attacker bypasses Basic Auth, filesystem boundaries remain enforced. The approach aligns with secure coding guidance for file access and complements the scanner checks provided by tools such as middleBrick, which can identify path traversal and insecure file exposure findings during automated scans.

Frequently Asked Questions

Can Basic Auth alone prevent symlink attacks in Express?
No. Basic Auth protects the route entry point but does not secure filesystem operations. If route handlers use user-controlled input to build file paths without canonicalization and containment checks, attackers can traverse directories or follow malicious symlinks to access or overwrite sensitive files.
What is the most reliable way to prevent path traversal and symlink attacks in Express file endpoints?
Resolve all paths with path.resolve, enforce that the resolved path starts with a canonical base directory, avoid direct use of user-supplied filenames, and use a controlled mapping or token-based lookup for file access. Middleware should also validate and sanitize inputs before any fs operation.