Request Smuggling in Express
How Request Smuggling Manifests in Express
Request smuggling in Express applications typically occurs when there's a mismatch between how Express parses incoming HTTP requests and how a reverse proxy or load balancer interprets the same request. This desynchronization creates opportunities for attackers to hide malicious payloads or manipulate request boundaries.
The most common Express-specific scenario involves Express's built-in body parsing middleware. When you use express.json() or express.urlencoded(), Express reads the Content-Length header to determine how much data to parse. However, if a reverse proxy like Nginx or AWS ALB uses Transfer-Encoding instead, a discrepancy emerges.
const express = require('express');
const app = express();
// Vulnerable: Express uses Content-Length, but proxy might use Transfer-Encoding
app.use(express.json());
app.post('/api/data', (req, res) => {
// An attacker could smuggle a second request after the JSON body
console.log(req.body);
Consider this malicious request:
POST /api/data HTTP/1.1
Host: example.com
Content-Length: 4
Transfer-Encoding: chunked
1234
POST /admin/delete HTTP/1.1
Host: example.com
Content-Type: application/json
Content-Length: 25
{ "delete": "all" }
0
POST /api/data HTTP/1.1
Host: example.com
Content-Length: 10
{"key":Express reads only the first 4 bytes (Content-Length), processes the JSON, and leaves the remaining data in the stream. The proxy, however, processes the entire chunked request, potentially forwarding the hidden /admin/delete request to the backend.
Another Express-specific vulnerability occurs with multipart form data. Express's multer middleware can be confused by malformed boundaries:
const multer = require('multer');
const upload = multer();
app.post('/upload', upload.single('file'), (req, res) => {
// Malformed boundaries could allow smuggling
Attackers craft requests with overlapping or missing boundaries that different parsers interpret differently, potentially injecting commands or accessing unauthorized resources.
Express-Specific Detection
Detecting request smuggling in Express requires examining both your application code and infrastructure configuration. Start by auditing your middleware stack for potential parsing inconsistencies.
Code review should focus on these Express patterns:
// Check for multiple body parsers that could conflict
app.use(express.json()); // Uses Content-Length
app.use(express.raw({ type: '*/*' })); // Might read differentlyLook for scenarios where different parts of your application might parse the same request differently. This often happens when you have conditional middleware or when integrating with third-party services.
middleBrick's scanner specifically tests for Express request smuggling by sending malformed requests with conflicting headers:
POST /test HTTP/1.1
Host: your-api.com
Content-Length: 5
Transfer-Encoding: chunked
0
POST /secret HTTP/1.1
Host: your-api.com
Content-Length: 15
{ "cmd": "drop" }The scanner analyzes responses for signs of desynchronization, such as:
- Unexpected status codes from endpoints that shouldn't be reachable
- Partial responses or timeouts
- Application errors that suggest request corruption
- Headers that indicate request mixing
middleBrick also examines your OpenAPI spec for potential smuggling vectors. If your spec defines conflicting content types or ambiguous body structures, it flags these for review:
paths: {
'/api/data': {
post: {
'consumes': ['application/json', 'application/x-www-form-urlencoded'],
'parameters': [{ 'in': 'body', 'schema': {...} }]
}
}
}This configuration ambiguity can lead to different parsers handling the same request in incompatible ways.
Express-Specific Remediation
Fixing request smuggling in Express applications requires a defense-in-depth approach. Start with strict header validation:
const express = require('express');
const app = express();
// Custom middleware to prevent smuggling
app.use((req, res, next) => {
// Reject requests with both Content-Length and Transfer-Encoding
return res.status(400).json({ error: 'Invalid headers' });
}
// Validate Content-Length is a reasonable number
1000000) { // 1MB limit
return res.status(413).json({ error: 'Payload too large' });
}
next();
Standardize on a single body parser and configure it securely:
// Use a single, well-configured parser
app.use(express.json({
limit: '1mb',
strict: true // Reject non-strict JSON
}));
// If you need form data, use it explicitly
const urlencodedParser = express.urlencoded({ extended: false, limit: '1mb' });For applications that must handle multiple content types, use route-specific middleware to ensure consistent parsing:
app.post('/api/json', express.json(), (req, res) => {
// Only JSON allowed here
app.post('/api/form', urlencodedParser, (req, res) => {
// Only form data allowed here
When using reverse proxies, ensure they're configured to use a single transfer mechanism. For Nginx, this means:
location /api/ {
# Force Content-Length and reject Transfer-Encoding
}middleBrick's Pro plan includes continuous monitoring that periodically tests your Express endpoints for regression. If a deployment introduces new smuggling vulnerabilities, you'll get alerts before attackers can exploit them.
For comprehensive protection, combine these Express-specific fixes with infrastructure-level controls. Ensure all proxies in your request chain are configured identically, and consider using a WAF that specifically detects smuggling patterns.