Injection Flaws in Express
How Injection Flaws Manifest in Express
Injection flaws in Express applications occur when untrusted data is sent to an interpreter as part of a command or query. Express, being a flexible Node.js framework, exposes several injection vectors that developers must guard against.
The most common injection flaw in Express is SQL injection through database queries. When using raw SQL with user input, attackers can manipulate queries to access unauthorized data or modify database contents. For example:
const userId = req.query.id; // User provides: 1 OR 1=1 --
const query = `SELECT * FROM users WHERE id = ${userId}`;
This query becomes SELECT * FROM users WHERE id = 1 OR 1=1 --, returning all users instead of a single record.
Command injection is another critical threat when Express applications execute shell commands with user input. Using child_process.exec() without proper sanitization allows attackers to append malicious commands:
const { exec } = require('child_process');
const filename = req.query.file; // User provides: file.txt; rm -rf /
exec(`cat ${filename}`, (err, stdout) => {
console.log(stdout);
});
Object injection can occur when Express applications deserialize untrusted data or use eval() on user input. Express's res.render() with dynamic template paths is particularly vulnerable:
const template = req.query.template; // User provides: ../../../etc/passwd
res.render(template, data);
Template injection in Express using engines like Pug or EJS can also lead to code execution if user input reaches the template context unsanitized.
Cross-site scripting (XSS) is technically a form of injection where malicious scripts are injected into web pages viewed by other users. Express applications must properly escape output to prevent XSS attacks.
Express-Specific Detection
Detecting injection flaws in Express requires both static analysis and dynamic testing. Static analysis tools like ESLint with security plugins can catch obvious injection patterns, but dynamic testing is essential for comprehensive coverage.
middleBrick's black-box scanning approach is particularly effective for Express applications because it tests the actual running API without requiring source code access. The scanner sends malicious payloads to your Express endpoints and analyzes responses for signs of successful injection.
For SQL injection detection, middleBrick tests common injection patterns like:
--', '); DELETE FROM users; --
' OR '1'='1
1' UNION SELECT password FROM users
The scanner analyzes response times, error messages, and returned data to identify vulnerable queries. Express applications that leak database error details provide attackers with valuable information for crafting exploits.
Command injection testing includes payloads that check for shell command execution:
; ls -la
& cat /etc/passwd
| whoami
middleBrick's scanning infrastructure tests these patterns across all Express endpoints, including those behind authentication, to provide a complete security assessment.
Template injection detection involves submitting payloads that attempt to break out of template contexts. For Pug templates, middleBrick tests patterns like:
- console.log('test')
#{JSON.stringify(global)}
The scanner also checks for unsafe consumption patterns where Express applications process external data without validation, a common precursor to injection attacks.
Express-Specific Remediation
Express provides several native features and integrates with libraries that help prevent injection flaws. The most critical remediation is using parameterized queries instead of string concatenation for database operations.
For SQL injection prevention, use prepared statements with parameterized queries:
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME
});
async function getUserById(req, res) {
const userId = req.params.id;
try {
const [rows] = await pool.execute(
'SELECT * FROM users WHERE id = ?',
[userId]
);
res.json(rows);
} catch (error) {
res.status(500).json({ error: 'Database error' });
}
}
Notice the ? placeholder that safely handles user input without allowing SQL injection.
For command injection prevention, avoid exec() and eval() entirely. Use child_process.spawn() with argument arrays, or better yet, use native Node.js libraries for file operations:
const fs = require('fs').promises;
async function readFileSafe(req, res) {
const filename = req.query.file;
const basePath = '/safe/directory/';
// Validate filename is safe
if (filename.includes('..') || !/^[a-zA-Z0-9_.-]+$/.test(filename)) {
return res.status(400).json({ error: 'Invalid filename' });
}
try {
const content = await fs.readFile(basePath + filename, 'utf8');
res.send(content);
} catch (error) {
res.status(404).json({ error: 'File not found' });
}
}
Express's built-in express.json() and express.urlencoded() middleware help prevent some injection by properly parsing request bodies. Always use these instead of custom parsers.
For template injection prevention, validate template paths and avoid dynamic template selection based on user input:
const allowedTemplates = ['userProfile', 'adminDashboard'];
function renderTemplate(req, res) {
const template = req.query.template;
if (!allowedTemplates.includes(template)) {
return res.status(400).json({ error: 'Invalid template' });
}
res.render(template, { data: sanitizeData(req.body) });
}
Always sanitize data before passing it to templates. Use libraries like DOMPurify for HTML sanitization when rendering user-generated content.