Protecting Against Injection Attacks: SQL, NoSQL, and XSS
Prevent SQL injection, NoSQL injection, and XSS attacks with validated code patterns. Covers parameterized queries, input sanitization, and CSP configuration.
Introduction
Injection attacks remain the most dangerous web application vulnerabilities. They occur when untrusted data is sent to an interpreter as part of a command or query.
SQL Injection Prevention
Parameterized Queries
// ❌ NEVER: String concatenation const query = `SELECT * FROM users WHERE email = '${email}'`; // ✅ ALWAYS: Parameterized queries // Using pg (node-postgres) const result = await pool.query( 'SELECT * FROM users WHERE email = $1', [email] ); // Using Prisma const user = await prisma.user.findUnique({ where: { email }, }); // Using Knex const users = await knex('users') .where('email', email) .first(); // Using TypeORM const user = await userRepository.findOne({ where: { email }, });
Dynamic Query Building
// Safe dynamic query building with Knex function buildSearchQuery(filters: SearchFilters) { let query = knex('products').select('*'); if (filters.category) { query = query.where('category', filters.category); } if (filters.minPrice !== undefined) { query = query.where('price', '>=', filters.minPrice); } if (filters.maxPrice !== undefined) { query = query.where('price', '<=', filters.maxPrice); } if (filters.search) { // Safe full-text search query = query.whereRaw( 'to_tsvector(name || \' \' || description) @@ plainto_tsquery(?)', [filters.search] ); } // Safe dynamic ordering const allowedSortFields = ['name', 'price', 'created_at']; if (filters.sortBy && allowedSortFields.includes(filters.sortBy)) { query = query.orderBy(filters.sortBy, filters.sortOrder === 'desc' ? 'desc' : 'asc'); } return query; }
NoSQL Injection Prevention
// ❌ Vulnerable to NoSQL injection const user = await db.collection('users').findOne({ username: req.body.username, password: req.body.password, // Attacker can send { "$gt": "" } }); // ✅ Validate and sanitize input types import { z } from 'zod'; const loginSchema = z.object({ username: z.string().min(1).max(50), password: z.string().min(1).max(100), }); const { username, password } = loginSchema.parse(req.body); // Now safe - values are guaranteed to be strings const user = await db.collection('users').findOne({ username, password: await bcrypt.hash(password, hashedPassword), }); // ✅ Use MongoDB's strict query operators const user = await db.collection('users').findOne({ username: { $eq: username }, // Explicit equality });
XSS Prevention
Output Encoding
import DOMPurify from 'isomorphic-dompurify'; import { encode } from 'html-entities'; // For plain text output function escapeHtml(text: string): string { return encode(text); } // For rich text that needs some HTML function sanitizeHtml(html: string): string { return DOMPurify.sanitize(html, { ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'], ALLOWED_ATTR: ['href', 'title'], ALLOW_DATA_ATTR: false, }); } // React automatically escapes by default function UserProfile({ user }: { user: User }) { return ( <div> {/* Safe - React escapes this */} <h1>{user.name}</h1> {/* ❌ Dangerous - bypasses React's escaping */} <div dangerouslySetInnerHTML={{ __html: user.bio }} /> {/* ✅ Safe - sanitize first */} <div dangerouslySetInnerHTML={{ __html: sanitizeHtml(user.bio) }} /> </div> ); }
Content Security Policy
// Strict CSP that prevents inline scripts app.use((req, res, next) => { const nonce = crypto.randomBytes(16).toString('base64'); res.locals.nonce = nonce; res.setHeader('Content-Security-Policy', [ "default-src 'self'", `script-src 'self' 'nonce-${nonce}'`, "style-src 'self' 'unsafe-inline'", "img-src 'self' data: https:", "connect-src 'self' https://api.example.com", "frame-ancestors 'none'", "base-uri 'self'", "form-action 'self'", ].join('; ')); next(); });
Command Injection Prevention
import { exec, execFile } from 'child_process'; // ❌ NEVER: Shell command with user input exec(`convert ${userFilename} output.png`); // ✅ Use execFile with arguments array execFile('convert', [userFilename, 'output.png'], (error, stdout) => { // Safe - arguments are not interpreted by shell }); // ✅ Better: Avoid shell entirely import sharp from 'sharp'; await sharp(userFilename) .resize(800, 600) .toFile('output.png');
Conclusion
Injection prevention requires:
- Never trust user input - validate and sanitize everything
- Use parameterized queries - never concatenate SQL
- Validate data types - especially for NoSQL
- Encode output - context-appropriate encoding
- Implement CSP - defense in depth against XSS
- Avoid shell commands - use libraries instead
Defense in depth is key - multiple layers of protection ensure that if one fails, others still protect you.
Related Articles
Security Engineering18 min read
API Security Hardening: A Practitioner's Guide
Secure your APIs with rate limiting, input validation, and CORS configuration. Production-tested checklist covering authentication, encryption, and error handling.
Security Engineering21 min read
Authentication and Authorization in Production Systems
Implement secure JWT authentication with refresh token rotation, RBAC, and OAuth 2.0 flows. Production patterns from healthcare and government systems.
Security Engineering15 min read
Secure Session Management: Patterns and Pitfalls
Implement secure session management with proper cookie settings, token rotation, and logout flows. Covers session fixation, hijacking prevention, and multi-device handling.
Backend Design18 min read
Laravel at Scale: Enterprise Patterns Beyond MVC
Build enterprise Laravel applications with repository pattern, service layer, and DDD principles. Production patterns from government and healthcare systems.
Backend Design20 min read
Database Design Patterns for Scale
Scale databases with sharding, replication, and partitioning. Covers PostgreSQL, MySQL, and MongoDB scaling patterns with real performance numbers from production systems.