SQL injection remains one of the most dangerous and prevalent web application vulnerabilities, even decades after its discovery. Despite being well-documented and easily preventable, SQL injection attacks continue to compromise databases worldwide.
What is SQL Injection?
SQL injection occurs when an attacker manipulates a SQL query by inserting malicious input into a web form, URL parameter, or API request. This allows the attacker to execute arbitrary SQL commands on your database.
Example Attack
Consider this vulnerable login code:
const email = req.body.email;
const password = req.body.password;
const query = `SELECT * FROM users WHERE email = '${email}' AND password = '${password}'`;
const user = db.query(query);
An attacker could input admin@example.com' OR '1'='1 as the email, resulting in this query:
SELECT * FROM users WHERE email = 'admin@example.com' OR '1'='1' AND password = 'anything'
Since '1'='1' is always true, this bypasses authentication entirely.
Types of SQL Injection
1. In-Band SQLi (Classic)
The attacker uses the same communication channel to launch the attack and gather results. Most common and easiest to exploit.
2. Inferential SQLi (Blind)
No data is transferred via the web application, but the attacker can reconstruct the database structure by observing behavior and response times.
3. Out-of-Band SQLi
The attacker uses different channels to launch the attack and gather results. Rare but dangerous when possible.
Prevention Techniques
1. Parameterized Queries (Prepared Statements)
The gold standard for preventing SQL injection. The database distinguishes between code and data, regardless of input.
Node.js (MySQL):
// ✅ Safe
const query = 'SELECT * FROM users WHERE email = ? AND password = ?';
db.query(query, [email, password], (err, results) => {
// Handle results
});
Python:
# ✅ Safe
cursor.execute(
"SELECT * FROM users WHERE email = %s AND password = %s",
(email, password)
)
PHP (PDO):
// ✅ Safe
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ? AND password = ?');
$stmt->execute([$email, $password]);
2. Use ORMs Correctly
ORMs like Sequelize, TypeORM, SQLAlchemy provide parameterization by default, but can still be vulnerable if used incorrectly.
// ❌ Vulnerable even with ORM
const users = await User.findAll({
where: sequelize.literal(`email = '${email}'`)
});
// ✅ Safe ORM usage
const users = await User.findAll({
where: { email: email }
});
3. Input Validation
While not a primary defense, validating input adds an extra layer. Reject input that doesn't match expected patterns.
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
throw new Error('Invalid email format');
}
4. Least Privilege Principle
Database users should have minimal permissions. Your application's database user shouldn't be able to drop tables or access system tables.
-- ✅ Create limited user
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'strong_password';
GRANT SELECT, INSERT, UPDATE ON app_db.* TO 'app_user'@'localhost';
-- No DROP, CREATE, or admin privileges
5. Use Stored Procedures (With Caution)
Stored procedures can help, but only if they use parameterization internally. Don't assume stored procedures are automatically safe.
Detection and Monitoring
Automated Scanning
Static analysis tools can detect SQL injection vulnerabilities before they reach production. ScanMyCode.dev automatically scans your codebase for:
- String concatenation in SQL queries
- Missing parameterization
- Unsafe ORM usage
- Template literal SQL queries
Runtime Protection
Web Application Firewalls (WAF) can detect and block SQL injection attempts in real-time. Common patterns include:
- SQL keywords (UNION, SELECT, DROP)
- Comment characters (-- , /* */)
- Special characters (' , " , ;)
Logging and Alerts
Monitor for suspicious database activity:
// Log all database errors
db.on('error', (err) => {
logger.error('Database error:', {
error: err.message,
stack: err.stack,
query: err.sql
});
// Alert security team for injection patterns
if (err.sql?.includes('UNION') || err.sql?.includes('--')) {
securityAlert('Possible SQL injection attempt', err);
}
});
Real-World Consequences
SQL injection isn't just theoretical. Major breaches have occurred due to unpatched SQL injection vulnerabilities:
- 2017 Equifax breach: 147 million records exposed
- 2019 First American Financial: 885 million sensitive documents
- 2020 Freepik: 8.3 million user records
Testing for Vulnerabilities
Test your application with tools like:
- sqlmap: Automated SQL injection tool
- Burp Suite: Web vulnerability scanner
- OWASP ZAP: Open-source security scanner
- ScanMyCode.dev: Comprehensive code audit including SQL injection detection
Conclusion
SQL injection is entirely preventable with proper coding practices. The rules are simple:
- Always use parameterized queries
- Never concatenate user input into SQL strings
- Validate all input
- Use least privilege for database users
- Regularly scan your code for vulnerabilities
Don't leave your database exposed. Run a security audit to identify and fix SQL injection vulnerabilities in your codebase.