HIGH header injectionexpress

Header Injection in Express

How Header Injection Manifests in Express

Header injection in Express applications typically occurs when user-controlled data flows into HTTP response headers without proper validation. This vulnerability can lead to HTTP response splitting, cache poisoning, and cross-site scripting attacks. Express's middleware architecture and request handling create specific attack surfaces that developers must understand.

The most common Express-specific scenario involves dynamic header setting based on request parameters. Consider this vulnerable pattern:

app.get('/set-header', (req, res) => {
  const headerName = req.query.name;
  const headerValue = req.query.value;
  res.set(headerName, headerValue);
  res.send('Header set');
});

An attacker can exploit this by sending: /set-header?name=Content-Type&value=text/html%0d%0a%0d%0a%3Cscript%3Ealert(1)%3C/script%3E. The %0d%0a sequences represent CRLF characters that break out of the header section and inject malicious content into the response body.

Another Express-specific pattern involves using res.redirect() with user input:

app.get('/redirect', (req, res) => {
  res.redirect(req.query.url);
});

This allows open redirect attacks and can be combined with header injection if the redirect URL contains CRLF sequences.

Express's res.append() method also creates injection risks when used with unsanitized input:

app.get('/append-header', (req, res) => {
  res.append('X-Custom-Header', req.query.data);
  res.send('Appended');
});

The vulnerability extends to template rendering where headers might be set dynamically based on template variables. Express's default view engine behavior doesn't automatically escape header values, making this a common oversight.

Middleware order matters significantly. If header-setting middleware runs before authentication checks, an attacker might inject headers that affect authentication flows or bypass security controls.

Express-Specific Detection

Detecting header injection in Express requires both static code analysis and dynamic testing. For static analysis, look for patterns where request parameters directly influence header values without validation.

Code review should identify:

  • Direct use of req.query, req.params, or req.body in res.set(), res.append(), or res.header()
  • Dynamic header names set from user input
  • URL parameters passed to res.redirect()
  • Template variables used in header contexts

Dynamic testing with tools like middleBrick specifically scans for header injection vulnerabilities by sending payloads containing CRLF sequences and observing responses. middleBrick's black-box scanning approach tests the actual running application without requiring source code access.

For Express applications, middleBrick performs these specific checks:

middlebrick scan https://yourapp.com --tests=header-injection

The scanner sends requests with encoded CRLF sequences (%0D%0A) in various parameters and headers, then analyzes responses for:

  • Unexpected headers appearing in responses
  • Response body content injection
  • HTTP response splitting where status codes or locations are manipulated
  • Cache poisoning indicators where injected headers affect caching behavior

middleBrick's API security scanning also checks for related issues like open redirects and unsafe consumption patterns that often accompany header injection vulnerabilities. The tool provides a security score (A-F) and specific findings with severity levels and remediation guidance.

During development, middleware like helmet can help detect potential header injection by enforcing strict header policies:

const helmet = require('helmet');
app.use(helmet({
  hsts: false // disable if not needed
}));

Helmet's content security policies and other protections can prevent some header injection impacts even when the underlying vulnerability exists.

Express-Specific Remediation

Remediating header injection in Express requires input validation, output encoding, and architectural changes to prevent unsafe data flows. The most effective approach combines multiple defense layers.

Input validation should be the first line of defense. Use a whitelist approach for header names and strict validation for header values:

const validHeaders = ['X-Custom-Header', 'X-Tracking-ID'];

app.get('/set-header', (req, res) => {
  const headerName = req.query.name;
  const headerValue = req.query.value;
  
  if (!validHeaders.includes(headerName)) {
    return res.status(400).send('Invalid header name');
  }
  
  if (/
|
/.test(headerValue)) {
    return res.status(400).send('Header value contains invalid characters');
  }
  
  res.set(headerName, headerValue);
  res.send('Header set');
});

The regular expression / | / detects carriage return and line feed characters that enable header injection. Always validate against CRLF sequences before using user input in headers.

For redirect functionality, implement a safe redirect mechanism:

const allowedRedirects = ['https://example.com', 'https://yourapp.com'];

app.get('/redirect', (req, res) => {
  const url = req.query.url;
  
  if (!allowedRedirects.includes(url)) {
    return res.status(400).send('Invalid redirect URL');
  }
  
  res.redirect(url);
});

Alternatively, use relative redirects or a URL validation library to ensure redirects stay within your domain.

Express's built-in res.location() method provides some protection, but always validate the input:

app.post('/callback', (req, res) => {
  const returnUrl = req.query.returnUrl || '/default';
  
  // Validate returnUrl is relative or in allowed list
  if (returnUrl.startsWith('http') && !allowedRedirects.some(domain => returnUrl.startsWith(domain))) {
    return res.status(400).send('Invalid return URL');
  }
  
  res.location(returnUrl);
  res.send('Processing complete');
});

For applications using template engines, ensure header values are properly escaped. With Pug/Jade:

//- Bad: unescaped header value
- res.set('X-Custom', headerValue)

//- Good: validate before setting
- var safeValue = headerValue.replace(/[^\w\s-]/g, '')
- res.set('X-Custom', safeValue)

Middleware-based validation provides centralized protection:

function validateHeaders(req, res, next) {
  const suspiciousHeaders = Object.keys(req.headers).filter(header =>
    /\r|\n/.test(header) || /\r|\n/.test(req.headers[header])
  );
  
  if (suspiciousHeaders.length > 0) {
    console.warn('Suspicious headers detected:', suspiciousHeaders);
    return res.status(400).send('Invalid request');
  }
  
  next();
}

app.use(validateHeaders);

Finally, implement comprehensive logging and monitoring. Log all header-setting operations with user context, making it easier to detect and investigate potential injection attempts.

Frequently Asked Questions

How does header injection differ from other injection vulnerabilities in Express?
Header injection specifically targets HTTP response headers using CRLF sequences to break out of header sections. Unlike SQL injection or XSS, it manipulates the HTTP protocol structure itself. Express's middleware architecture makes it particularly vulnerable when request parameters flow directly into header-setting functions without validation.
Can helmet middleware prevent all header injection attacks?
Helmet provides useful protections but doesn't prevent all header injection scenarios. It sets secure default headers and can block some malicious patterns, but developers must still validate input that flows into custom headers. Helmet is a defense-in-depth measure, not a complete solution for header injection vulnerabilities.