Header Injection in Express with Basic Auth
Header Injection in Express with Basic Auth — how this specific combination creates or exposes the vulnerability
Header Injection in Express when Basic Auth is used arises when untrusted input reaches HTTP header construction without proper validation or sanitization. Basic Auth typically relies on the Authorization header with a base64-encoded credential pair. If application code or middleware dynamically sets other headers (e.g., Location, WWW-Authenticate, X-Request-ID) using attacker-controlled data, it can lead to header injection. For example, reflecting user input in a Location header during a redirect can enable HTTP response splitting, smuggling requests, or cache poisoning because headers are separated by \r\n sequences.
In Express, this often occurs when developers concatenate or assign request-derived values into headers, such as res.set('Location', userSuppliedUrl) or res.header('X-Forwarded-Host', req.hostname) without validation. Even when Basic Auth protects an endpoint, an injected header can bypass intended protections by manipulating how intermediaries or clients interpret responses. Attack patterns include response splitting to inject malicious headers, CRLF injection to smuggle requests across a load balancer, or header-based redirects that leak credentials or cause open redirects. These issues are relevant to the BOLA/IDOR and BFLA/Privilege Escalation checks in middleBrick, which test whether endpoints leak information or allow privilege abuse via header manipulation.
Consider an Express route that uses Basic Auth but then sets a custom header based on query parameters:
const express = require('express');
const app = express();
const auth = require('basic-auth');
app.get('/profile', (req, res) => {
const user = auth(req);
if (!user || user.name !== 'alice' || user.pass !== 'secret') {
res.set('WWW-Authenticate', 'Basic realm="Access"');
return res.status(401).send('Unauthorized');
}
// Vulnerable: user-controlled value placed directly into a header
const referrer = req.query.referrer;
if (referrer) {
res.set('X-Redirect-Referrer', referrer);
}
res.json({ message: 'Profile data' });
});
An attacker can supply referrer with \r\n to inject additional headers, such as X-Content-Type-Options: nosniff, which may alter client behavior. While Basic Auth ensures credentials are present, it does not prevent header injection in downstream processing. The risk is compounded if responses are cached or forwarded by proxies that trust injected headers, enabling data exposure or SSRF-like behaviors aligned with middleBrick’s Data Exposure and SSRF checks.
middleBrick detects such patterns by analyzing OpenAPI specs and runtime behavior, identifying places where user input influences headers. Its LLM/AI Security checks also ensure that prompt or configuration leakage does not occur via headers, and its scans verify whether endpoints correctly handle malformed or malicious header values without exposing sensitive information.
Basic Auth-Specific Remediation in Express — concrete code fixes
To remediate header injection with Basic Auth in Express, treat all external inputs as untrusted and never directly assign request-derived data to HTTP headers. Use strict allowlists, avoid reflecting untrusted values, and rely on Express’s built-in facilities for safe header management.
Example of a vulnerable pattern and its fix:
// Vulnerable
app.get('/search', (req, res) => {
const token = req.query.token;
res.set('X-Custom-Token', token); // Injection risk
res.json({ ok: true });
});
// Fixed: validate and do not reflect untrusted input
app.get('/search', (req, res) => {
const token = req.query.token;
if (!token || !/^[A-Za-z0-9\-_]+={0,2}$/.test(token)) {
return res.status(400).send('Invalid token');
}
// Only set header if needed; prefer using a constant or hashed value
res.set('X-Request-ID', crypto.randomUUID());
res.json({ ok: true });
});
When using Basic Auth, keep Authorization header handling minimal and avoid augmenting it with untrusted data. If you must set WWW-Authenticate on 401 responses, use a static realm string and avoid echoing user input:
const auth = require('basic-auth');
app.get('/admin', (req, res) => {
const user = auth(req);
if (!user || user.name !== 'admin' || user.pass !== 's3cret') {
// Safe: static realm, no user input
res.set('WWW-Authenticate', 'Basic realm="AdminArea"');
return res.status(401).send('Unauthorized');
}
res.json({ secret: 'top-secret' });
});
For redirects, use absolute URLs from a whitelist or path-only values, and avoid setting Location from raw input. If you need to pass a return location, encode it server-side and validate on receipt:
const allowedHosts = ['https://app.example.com', 'https://dashboard.example.com'];
app.get('/login', (req, res) => {
const user = auth(req);
if (!user || user.name !== 'bob' || user.pass !== 'pass') {
res.set('WWW-Authenticate', 'Basic realm="Members"');
return res.status(401).send('Unauthorized');
}
const raw = req.query.next;
let nextUrl = '/dashboard'; // default safe path
try {
const url = new URL(raw, 'https://app.example.com');
if (allowedHosts.includes(url.origin)) {
nextUrl = url.pathname + url.search;
}
} catch (err) {
// ignore parse errors, use default
}
res.redirect(303, nextUrl);
});
Additional hardening steps include removing any middleware that echoes request-derived values into headers, enforcing strict Content-Security-Policy and Referrer-Policy headers, and using helmet to set secure defaults. middleBrick’s scans can validate these remediations by checking headers and responses for injection-prone patterns, ensuring compliance with OWASP API Top 10 and relevant regulatory mappings.