Integer Overflow in Express
How Integer Overflow Manifests in Express
Integer overflow vulnerabilities in Express applications typically emerge through numeric parameter handling, pagination logic, and resource allocation calculations. When Express parses request parameters using req.query or req.params, they arrive as strings that developers often convert to numbers without proper validation.
A common Express pattern that introduces integer overflow risk involves pagination endpoints:
app.get('/api/items', (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
// Vulnerable: no bounds checking
const offset = (page - 1) * limit;
// Database query with potentially massive offset
db.query('SELECT * FROM items LIMIT ? OFFSET ?', [limit, offset])
.then(results => res.json(results))
.catch(err => res.status(500).json({ error: err.message }));
});In this Express route, an attacker can supply extremely large values for page or limit. In JavaScript, numbers are represented as 64-bit floating-point values, but when these values interact with systems expecting 32-bit integers (like certain databases or C-based libraries), overflow conditions occur. A request like /api/items?page=999999999999999999&limit=999999999999999999 causes the multiplication (page - 1) * limit to produce Infinity, which then breaks database queries or causes memory allocation errors.
Another Express-specific manifestation occurs in file upload handling:
app.post('/upload', upload.single('file'), (req, res) => {
const chunkCount = parseInt(req.query.chunks) || 1;
const chunkSize = parseInt(req.query.size) || 1024;
// Vulnerable: no validation of chunk calculations
const totalSize = chunkCount * chunkSize;
if (totalSize > MAX_ALLOWED_SIZE) {
return res.status(400).json({ error: 'File too large' });
}
// Process upload...
});Here, an attacker can trigger integer overflow by providing values that multiply to exceed JavaScript's safe integer range (2^53 - 1), causing unexpected behavior in size calculations and potentially bypassing upload limits.
Express middleware that processes numeric headers or body parameters faces similar risks. The body-parser middleware converts JSON numbers to JavaScript numbers, but if those numbers represent sizes, counts, or limits for external systems, overflow vulnerabilities emerge at the integration boundaries.
Express-Specific Detection
Detecting integer overflow in Express applications requires examining both the application code and runtime behavior. Static analysis tools can identify risky numeric conversions and calculations:
const vulnerablePatterns = [
// Direct parseInt without radix
/parseInt\s*\(\s*[^)]+\s*\)/,
// Multiplication without bounds checking
/\*\s*[^=]/,
// Array access with calculated indices
/\[\s*[^\]]*\*[^\]]*\s*\]/
];Runtime detection in Express can leverage middleware that validates numeric parameters:
function validateNumericParams(allowedParams, maxValues) {
return (req, res, next) => {
for (const [param, maxValue] of Object.entries(allowedParams)) {
if (req.query[param] !== undefined) {
const value = parseInt(req.query[param]);
if (isNaN(value) || value < 0 || value > maxValue) {
return res.status(400).json({
error: `Invalid value for ${param}: must be between 0 and ${maxValue}`
});
}
// Check for potential overflow in common operations
if (value > 1e12) { // Arbitrary large threshold
console.warn(`Suspiciously large value for ${param}: ${value}`);
}
}
}
next();
};
}
// Usage
app.use('/api/safe', validateNumericParams(
{ page: 1000000, limit: 1000, count: 1000000 },
{ page: 1000000, limit: 1000, count: 1000000 }
));middleBrick's black-box scanning approach specifically tests Express endpoints for integer overflow by sending boundary values and observing responses. The scanner sends requests with extremely large numeric parameters and analyzes whether the application handles them safely, returns appropriate error messages, or exhibits abnormal behavior like crashes or timeouts.
For Express applications using TypeScript, the compiler can help catch some overflow-related issues, but runtime validation remains essential since JavaScript numbers don't have fixed integer sizes:
function safeMultiply(a: number, b: number, max: number = Number.MAX_SAFE_INTEGER): number {
if (a > max || b > max) {
throw new Error('Input exceeds safe limits');
}
const result = a * b;
if (!Number.isSafeInteger(result)) {
throw new Error('Multiplication result exceeds safe integer range');
}
return result;
}
// Express route using safe multiplication
app.get('/api/calculate', (req, res) => {
try {
const a = parseInt(req.query.a);
const b = parseInt(req.query.b);
const result = safeMultiply(a, b);
res.json({ result });
} catch (err) {
res.status(400).json({ error: err.message });
}
});Express-Specific Remediation
Remediating integer overflow in Express applications requires a defense-in-depth approach combining input validation, safe arithmetic operations, and proper error handling. The foundation is strict parameter validation using Express middleware:
const { body, query, validationResult } = require('express-validator');
// Validation chain for numeric parameters
const validatePagination = [
query('page')
.optional({ checkFalsy: true })
.isInt({ min: 1, max: 1000 })
.withMessage('Page must be an integer between 1 and 1000'),
query('limit')
.optional({ checkFalsy: true })
.isInt({ min: 1, max: 100 })
.withMessage('Limit must be an integer between 1 and 100'),
(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
next();
}
];
// Secure Express route
app.get('/api/items', validatePagination, (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const offset = (page - 1) * limit;
// Safe database query with validated parameters
db.query('SELECT * FROM items LIMIT ? OFFSET ?', [limit, offset])
.then(results => res.json(results))
.catch(err => {
console.error('Database error:', err);
res.status(500).json({ error: 'Internal server error' });
});
});For arithmetic operations that might overflow, use BigInt in Node.js environments when dealing with very large numbers:
app.post('/api/process-large-numbers', (req, res) => {
try {
// Use BigInt for safe arithmetic
const a = BigInt(req.body.a);
const b = BigInt(req.body.b);
// Check for overflow before operations
if (a > BigInt(Number.MAX_SAFE_INTEGER) ||
b > BigInt(Number.MAX_SAFE_INTEGER)) {
return res.status(400).json({
error: 'Numbers exceed safe integer range'
});
}
const result = a * b;
// Convert back to regular number if within safe range
if (result > BigInt(Number.MAX_SAFE_INTEGER)) {
return res.status(400).json({
error: 'Result exceeds safe integer range'
});
}
res.json({ result: Number(result) });
} catch (err) {
res.status(400).json({ error: 'Invalid number format' });
}
});Express applications often interact with external systems where integer sizes differ. When communicating with databases, APIs, or other services, validate that numeric values fit within expected ranges:
function validateInt32(value, paramName) {
if (!Number.isInteger(value)) {
throw new Error(`${paramName} must be an integer`);
}
if (value < -2147483648 || value > 2147483647) {
throw new Error(`${paramName} exceeds 32-bit integer range`);
}
return value;
}
app.get('/api/external-service', (req, res) => {
try {
const userId = validateInt32(parseInt(req.query.user_id), 'user_id');
const count = validateInt32(parseInt(req.query.count), 'count');
// Safe call to external service with validated integers
externalService.fetchData(userId, count)
.then(data => res.json(data))
.catch(err => res.status(502).json({ error: 'Service unavailable' }));
} catch (err) {
res.status(400).json({ error: err.message });
}
});For Express applications using TypeScript, leverage type safety to prevent accidental numeric operations:
type SafeInt = number & { __brand: 'SafeInt' };
function createSafeInt(value: number): SafeInt {
if (!Number.isInteger(value)) {
throw new Error('Value must be an integer');
}
if (value < -Math.pow(2, 31) || value > Math.pow(2, 31) - 1) {
throw new Error('Value exceeds 32-bit integer range');
}
return value as SafeInt;
}
// Express route with type-safe integers
app.get('/api/safe-math', (req, res) => {
try {
const a = createSafeInt(parseInt(req.query.a));
const b = createSafeInt(parseInt(req.query.b));
// Type system ensures these are safe integers
const result: SafeInt = (a + b) as SafeInt;
res.json({ result: Number(result) });
} catch (err) {
res.status(400).json({ error: err.message });
}
});