HIGH time of check time of useexpressbasic auth

Time Of Check Time Of Use in Express with Basic Auth

Time Of Check Time Of Use in Express with Basic Auth — how this specific combination creates or exposes the vulnerability

Time Of Check Time Of Use (TOCTOU) occurs when the outcome of a security decision depends on the timing between a check and the subsequent use of a resource. In Express applications that use HTTP Basic Auth, this commonly manifests when authorization checks are performed separately from the actual resource access, creating a window where state can change.

Consider an endpoint that first validates credentials and then reads a user-controlled identifier to fetch a resource. An attacker can exploit this by changing the resource identifier between the authorization check and the file or data access. Because Basic Auth credentials are decoded from a base64-encoded header on each request, the authentication itself is stateless; the vulnerability arises in how the application links authentication to authorization and resource handling.

For example, an application might authenticate a user, verify that the user has permission to view a document, and then use a filename from user input to read from disk. If the filename is mutable between the permission check and the filesystem read, an authenticated user could leverage a privileged file path (such as /etc/passwd) by altering the request between the check and the read. This is especially relevant when endpoints perform a lightweight existence or ownership check using one request parameter and then open a different resource based on a second parameter that can be manipulated later.

An attacker may also chain this with other patterns such as BOLA/IDOR by authenticating as a low-privilege account, passing an initial check, then changing an ID in a subsequent call to access another user’s data. In an Express route, this can look like authenticating once, storing a decoded user payload in the request, and then trusting an object ID from the URL or body without re-verifying scope on each operation.

Because middleBrick scans the unauthenticated attack surface, it can flag endpoints where authentication headers are accepted but the route logic does not enforce strict context-bound authorization on every use. The scanner’s checks for Authentication and BOLA/IDOR, combined with Property Authorization and Input Validation, are designed to surface these mismatches. Findings include evidence such as mismatched authorization decisions and untrusted input used in sensitive operations, alongside remediation guidance to bind access checks closely to the point of use.

Basic Auth-Specific Remediation in Express — concrete code fixes

To mitigate TOCTOU in Express with Basic Auth, ensure that authorization is re-evaluated immediately before any sensitive action and that all inputs are validated and sanitized in a single, atomic step. Avoid caching authorization decisions across multiple operations or requests, and do not trust decoded user information beyond the scope of a single, well-contained handler.

Use middleware to parse and validate credentials on each request, then perform authorization checks inline with the resource operation. This keeps the check and use tightly coupled and reduces the window for state manipulation. Below are concrete, working examples demonstrating secure patterns.

Insecure pattern to avoid:

// Insecure: authorization check separated from file read
app.get('/documents/:id', (req, res) => {
  const user = authenticateBasic(req); // decodes auth header
  if (!user) return res.sendStatus(401);

  // Perform a permission check (e.g., check DB or object ownership)
  const hasAccess = checkDocumentAccess(user.id, req.params.id);
  if (!hasAccess) return res.sendStatus(403);

  // TOCTOU risk: attacker can change behavior between check and read
  const filePath = getUserFile(req.params.id); // uses same ID but may be mutable
  fs.readFile(filePath, (err, data) => {
    if (err) return res.sendStatus(500);
    res.send(data);
  });
});

Secure pattern with immediate authorization and strict input validation:

const express = require('express');
const cors = require('cors');
const basicAuth = require('express-basic-auth');
const fs = require('fs').promises;
const path = require('path');

const app = express();
app.use(cors());
app.use(express.json());

// Configure Basic Auth with a user store and optional challenge
app.use(basicAuth({
  users: { alice: 'p4ssw0rd', bob: 's3cret' },
  challenge: true,
  realm: 'Documents API',
}));

// Secure route: bind authorization to the resource operation
app.get('/documents/:id', async (req, res) => {
  // The auth middleware attaches req.auth; ensure it exists
  if (!req.auth || !req.auth.user) {
    return res.status(401).json({ error: 'Unauthorized' });
  }

  // Validate and sanitize input early
  const documentId = req.params.id;
  if (!/^[a-f0-9-]{36}$/i.test(documentId)) {
    return res.status(400).json({ error: 'Invalid document identifier' });
  }

  // Re-evaluate authorization immediately before use
  const ownership = await getDocumentOwnership(documentId);
  if (!ownership || ownership.userId !== req.auth.user) {
    return res.status(403).json({ error: 'Forbidden' });
  }

  // Construct file path within a controlled directory using the validated ID
  const baseDir = path.resolve(__dirname, 'documents');
  const filePath = path.join(baseDir, `${documentId}.txt`);

  // Ensure the resolved path remains within baseDir (path traversal defense)
  if (!filePath.startsWith(baseDir + path.sep)) {
    return res.status(403).json({ error: 'Forbidden path' });
  }

  try {
    const data = await fs.readFile(filePath, 'utf8');
    res.json({ id: documentId, content: data });
  } catch (err) {
    if (err.code === 'ENOENT') {
      return res.status(404).json({ error: 'Not found' });
    }
    res.status(500).json({ error: 'Internal server error' });
  }
});

async function getDocumentOwnership(documentId) {
  // Simulated DB lookup; in practice, query with parameterized inputs
  const map = {
    'a1b2c3d4-e5f6-7890-abcd-ef1234567890': { userId: 'alice' },
    'b2c3d4e5-f6a7-8901-bcde-f12345678901': { userId: 'bob' },
  };
  return map[documentId] || null;
}

app.listen(3000, () => console.log('Server running on port 3000'));

Key practices reflected in the secure example:

  • Authenticate on each request and do not rely on cached authorization state across operations.
  • Validate and sanitize all inputs before use, using strict patterns for identifiers.
  • Perform authorization immediately before the sensitive action (file read), using the same validated identifiers.
  • Constrain file paths to a controlled directory and reject any traversal attempts to prevent path manipulation attacks.

These measures reduce the TOCTOU window by ensuring the decision to access a resource is made as close as possible to the access itself and that inputs remain immutable between validation and use.

Frequently Asked Questions

Can TOCTOU be exploited even when using HTTPS and Basic Auth?
Yes. HTTPS protects credentials in transit but does not prevent application-level TOCTOU. If authorization checks are separated from resource access, an authenticated attacker can manipulate mutable identifiers or state between the check and the use.
Does middleBrick fix TOCTOU findings?
middleBrick detects and reports TOCTOU and related authorization issues with severity and remediation guidance. It does not automatically fix or patch endpoints; developers must apply the recommended code changes.