Out Of Bounds Write in Express
How Out Of Bounds Write Manifests in Express
Out Of Bounds Write (OOBW) in Express applications typically occurs when user-controlled data is used to index into arrays, buffers, or other data structures without proper bounds checking. Express's asynchronous nature and middleware architecture create unique attack vectors for OOBW vulnerabilities.
The most common Express-specific OOBW scenario involves array indexing in route handlers where parameters directly control array access. Consider this vulnerable pattern:
app.get('/items/:index', (req, res) => {
const items = ['apple', 'banana', 'cherry'];
const index = parseInt(req.params.index);
res.json({ item: items[index] });
});Here, a negative index like -1 returns the last element, -2 returns the second-to-last, and indices beyond the array length return undefined. While this might seem harmless, it can lead to information disclosure or, in more complex scenarios, memory corruption if the underlying data structures are manipulated.
Another Express-specific OOBW pattern emerges in middleware chains. Express processes middleware sequentially, and an OOBW in one middleware can affect subsequent ones:
app.use((req, res, next) => {
req.userPermissions = ['read', 'write', 'delete'];
next();
});
app.get('/admin', (req, res) => {
const permIndex = parseInt(req.query.perm);
const hasPermission = req.userPermissions[permIndex] === 'admin';
if (hasPermission) {
res.json({ secret: 'admin-data' });
} else {
res.status(403).send('Forbidden');
}
});In this case, an out-of-bounds read might allow privilege escalation if the memory beyond the array contains unexpected values.
Buffer operations in Express routes present another critical OOBW vector. When handling file uploads or binary data:
app.post('/upload', (req, res) => {
const buffer = Buffer.alloc(100);
const size = parseInt(req.body.size) || 0;
const data = req.body.data;
// Vulnerable: no bounds checking
for (let i = 0; i < size; i++) {
buffer[i] = data[i];
}
res.json({ status: 'uploaded' });
});If size exceeds 100, this writes beyond the allocated buffer, potentially corrupting memory or allowing arbitrary data injection.
Express-Specific Detection
Detecting OOBW vulnerabilities in Express requires both static analysis and runtime testing. For static analysis, look for patterns where user input directly indexes arrays or buffers without validation:
# Search for dangerous patterns
grep -r "\[\s*[^]]*req\.[a-zA-Z.]*\s*\]" routes/ --include="*.js"
grep -r "Buffer\.alloc" routes/ --include="*.js" -A 5Runtime detection involves testing with boundary values. For array indexing vulnerabilities:
// Test with negative indices, indices at array length, and indices beyond length
const testCases = [-1, 0, 2, 3, 100];
const results = testCases.map(index => {
const url = `/items/${index}`;
return fetch(url).then(r => r.json());
});middleBrick's black-box scanning approach is particularly effective for Express OOBW detection because it tests the actual running application without requiring source code access. The scanner sends boundary value requests and analyzes responses for:
- Unexpected data exposure (e.g., out-of-bounds array elements)
- Application crashes or error messages revealing internal state
- Memory corruption indicators in binary responses
- Timing differences that suggest memory access patterns
For Express applications, middleBrick specifically tests the 12 security categories including Input Validation (to catch missing bounds checks) and Data Exposure (to detect information leakage from OOBW).
API specification analysis adds another detection layer. If your Express app uses OpenAPI/SwOAS specifications, middleBrick cross-references the spec with runtime behavior:
paths:
/items/{index}:
get:
parameters:
- name: index
in: path
schema:
type: integer
minimum: 0
maximum: 2 # Should match array boundsDiscrepancies between spec constraints and actual implementation indicate potential OOBW vulnerabilities.
Express-Specific Remediation
Express provides several native mechanisms to prevent OOBW vulnerabilities. The most fundamental is input validation using middleware:
const express = require('express');
const { body, param, validationResult } = require('express-validator');
const app = express();
// Validation middleware for array indices
const validateArrayIndex = (array) => [
param('index').isInt({ min: 0, max: array.length - 1 }),
(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ error: 'Invalid index' });
}
next();
}
];
const items = ['apple', 'banana', 'cherry'];
app.get('/items/:index',
validateArrayIndex(items),
(req, res) => {
const index = parseInt(req.params.index);
res.json({ item: items[index] });
}
);For buffer operations, use safe array methods and bounds checking:
app.post('/upload', (req, res) => {
const maxSize = 100;
const size = parseInt(req.body.size) || 0;
if (size < 0 || size > maxSize) {
return res.status(400).json({ error: 'Size out of bounds' });
}
const buffer = Buffer.alloc(size);
const data = req.body.data;
// Safe copy with bounds checking
const copySize = Math.min(size, data.length);
for (let i = 0; i < copySize; i++) {
buffer[i] = data[i];
}
res.json({ status: 'uploaded', bytesWritten: copySize });
});Express's built-in error handling can be leveraged for OOBW protection:
app.use((err, req, res, next) => {
console.error('Error:', err);
res.status(500).json({ error: 'Internal server error' });
});
// Safe array access wrapper
function safeArrayAccess(array, index, defaultValue = null) {
const idx = parseInt(index);
if (isNaN(idx) || idx < 0 || idx >= array.length) {
throw new Error('Index out of bounds');
}
return array[idx];
}
app.get('/admin', (req, res) => {
const permissions = ['read', 'write', 'delete'];
try {
const permIndex = parseInt(req.query.perm);
const permission = safeArrayAccess(permissions, permIndex);
if (permission === 'admin') {
res.json({ secret: 'admin-data' });
} else {
res.status(403).send('Forbidden');
}
} catch (error) {
res.status(400).json({ error: error.message });
}
});For complex applications, consider using TypeScript to catch OOBW at compile time:
interface Item {
id: number;
name: string;
}
const items: Item[] = [
{ id: 1, name: 'apple' },
{ id: 2, name: 'banana' },
{ id: 3, name: 'cherry' }
];
app.get('/items/:id', (req, res) => {
const id = parseInt(req.params.id);
const item = items.find(item => item.id === id);
if (!item) {
return res.status(404).json({ error: 'Item not found' });
}
res.json(item);
});This approach uses object lookup instead of array indexing, eliminating OOBW risks while providing better error handling.