Format String in Express
How Format String Manifests in Express
Format string vulnerabilities in Express applications typically arise when user-controlled data is passed directly into string formatting functions or logging mechanisms. In Express, this often occurs through:
- Dynamic logging statements using
console.logorwinstonwith unvalidated user input - Database query construction using string formatting
- Response messages that incorporate user data without proper sanitization
- Template rendering with unsafe interpolation
Consider this common Express pattern:
app.post('/api/user/:id', (req, res) => {
const userId = req.params.id;
console.log(`User ID: ${userId}`);
// Vulnerable logging
console.log(`User ID: %s`, userId);
// Vulnerable database query
const query = `SELECT * FROM users WHERE id = '${userId}'`;
// Vulnerable response message
res.send(`User ${userId} retrieved successfully`);
});
The critical issue occurs when userId contains format specifiers like %s, %d, or %x. In the logging example, if an attacker sends userId as %x, the logger attempts to read an additional argument that doesn't exist, potentially exposing memory contents or causing application crashes.
In Express applications, format string vulnerabilities become particularly dangerous because:
- Express middleware often logs request parameters by default
- Many Express applications use structured logging with format strings
- The framework's flexibility means developers frequently construct dynamic strings
Another Express-specific scenario involves error handling:
app.use((err, req, res, next) => {
console.error(`Error occurred: ${err.message}`);
// Vulnerable: error messages may contain format specifiers
console.error(`Error details: %s`, err.stack);
});
If err.message contains format specifiers, this could lead to information disclosure through the application logs.
Express-Specific Detection
Detecting format string vulnerabilities in Express applications requires both static analysis and runtime scanning. Here's how to identify these issues:
Static Code Analysis
Search your Express codebase for patterns like:
# Look for console.log with format specifiers
grep -r "console\.log(" --include="*.js" | grep -E "%[sd]"
# Check for template literals with user input
grep -r "`.*\$\{.*\}.*`" --include="*.js" | grep -E "req\.(params|query|body)"
# Find database queries using string concatenation
grep -r "SELECT.*'" --include="*.js" | grep -E "req\.(params|query|body)"
Runtime Detection with middleBrick
middleBrick's black-box scanning approach is particularly effective for detecting format string vulnerabilities in Express applications without requiring source code access:
# Scan your Express API endpoint
middlebrick scan https://your-api.example.com/api/users/123
# Scan with detailed output
middlebrick scan https://your-api.example.com/api/users/123 --format=json
middleBrick tests for format string vulnerabilities by:
- Submitting payloads containing format specifiers (%s, %d, %x, %n) in various input fields
- Analyzing responses for abnormal behavior or error messages
- Checking logging endpoints for information disclosure
- Testing database query endpoints for SQL injection that may reveal format string issues
Manual Testing
For Express applications, manually test format string vulnerabilities by:
# Test with format specifiers in URL parameters
curl "https://your-api.example.com/api/users/%s"
# Test with format specifiers in JSON body
curl -X POST https://your-api.example.com/api/users \
-H "Content-Type: application/json" \
-d '{"username": "%x", "password": "test"}'
Look for stack traces, application crashes, or unexpected output that might indicate a format string vulnerability.
Express-Specific Remediation
Remediating format string vulnerabilities in Express applications requires a multi-layered approach. Here are Express-specific solutions:
1. Safe Logging Practices
Replace vulnerable logging patterns with safe alternatives:
// Vulnerable
console.log(`User ID: ${userId}`);
console.log('User ID: %s', userId);
// Safe
console.log('User ID:', userId);
// Using structured logging
const winston = require('winston');
const logger = winston.createLogger({
format: winston.format.json(),
});
logger.info('User retrieved', { userId });
2. Parameterized Database Queries
Always use parameterized queries instead of string formatting:
// Vulnerable
const query = `SELECT * FROM users WHERE id = '${userId}'`;
// Safe with parameterized queries
const mysql = require('mysql2/promise');
const connection = await mysql.createConnection(dbConfig);
const [rows] = await connection.execute(
'SELECT * FROM users WHERE id = ?',
[userId]
);
// Using an ORM
const User = require('./models/User');
const user = await User.findOne({ where: { id: userId } });
3. Input Validation and Sanitization
Validate and sanitize all user inputs in Express middleware:
const express = require('express');
const app = express();
// Sanitize inputs
app.use(express.json({
verify: (req, res, buf) => {
const sanitized = buf.toString().replace(/%[sd]/g, '');
// Store sanitized version for logging
req.sanitizedBody = sanitized;
}
}));
// Validation middleware
function validateUserId(req, res, next) {
const userId = req.params.id;
// Check for format specifiers
if (/%[sd]/.test(userId)) {
return res.status(400).json({ error: 'Invalid user ID format' });
}
// Check if numeric
if (!/^[0-9]+$/.test(userId)) {
return res.status(400).json({ error: 'User ID must be numeric' });
}
next();
}
app.get('/api/users/:id', validateUserId, (req, res) => {
res.json({ success: true });
});
4. Safe Response Construction
Construct responses without incorporating user data directly:
// Vulnerable
res.send(`User ${userId} retrieved successfully`);
// Safe
res.json({
message: 'User retrieved successfully',
userId: userId
});
// Using template literals safely
const createResponse = (userId) => ({
message: `User ${userId} retrieved successfully`,
userId
});
5. Error Handling Best Practices
Implement safe error handling in Express:
// Safe error handling
app.use((err, req, res, next) => {
// Log error safely
console.error('Error occurred:', err.message);
// Send generic error response
res.status(500).json({
error: 'Internal server error',
requestId: req.id
});
});