SQL Injection
SQL injection is a code injection technique that exploits vulnerabilities in database queries by inserting malicious SQL statements into application input fields. Ranked as OWASP Top 10 A03:2021 (Injection), SQL injection remains one of the most dangerous and prevalent web application vulnerabilities. Attackers can bypass authentication, extract sensitive data, modify or delete records, execute administrative operations, and in severe cases, gain complete control over the database server.
What Is SQL Injection?
SQL injection occurs when user input is inserted directly into SQL queries without proper sanitization or parameterization. The vulnerability arises from the failure to separate code from data, allowing attackers to alter the structure and logic of SQL queries. Common attack vectors include:
Types of SQL Injection Attacks
Classic SQL Injection
CriticalDirect injection where error messages reveal database structure. Attacker sees immediate results.
' OR '1'='1
Blind SQL Injection
CriticalNo error messages displayed. Attacker infers data by observing application behavior (true/false responses).
' AND 1=1 -- (true) vs ' AND 1=2 -- (false)
Time-Based Blind
HighInfer data by measuring response time differences using database sleep functions.
'; IF (1=1) WAITFOR DELAY '00:00:05' --
UNION-Based Injection
CriticalUse UNION operator to combine results from malicious queries with legitimate query results.
' UNION SELECT username, password FROM users --
Stacked Queries
CriticalExecute multiple SQL statements by terminating the original query and appending new ones.
'; DROP TABLE users; --
Second-Order Injection
HighMalicious data stored safely initially, but executed unsafely in a different context later.
Stored username: admin'-- used in later query
Vulnerable vs Secure Code
Authentication Bypass
// DANGEROUS: Direct string concatenation
function loginUser(username, password) {
const query = `SELECT * FROM users WHERE username = '${username}' AND password = '${password}'`;
return db.execute(query);
}
// Attack: username = "admin' --"
// Result: SELECT * FROM users WHERE username = 'admin' --' AND password = ''
// The -- comments out the password check, bypassing authentication!// SAFE: Using parameterized queries
function loginUser(username, password) {
const query = 'SELECT * FROM users WHERE username = ? AND password = ?';
return db.execute(query, [username, password]);
}
// Or with named parameters (e.g., Node.js with pg)
function loginUser(username, password) {
const query = 'SELECT * FROM users WHERE username = $1 AND password = $2';
return db.query(query, [username, password]);
}
// Attack attempts are treated as literal strings, not SQL codeData Extraction
// DANGEROUS: Building queries with user input
function searchProducts(searchTerm) {
const query = "SELECT * FROM products WHERE name LIKE '%" + searchTerm + "%'";
return db.execute(query);
}
// Attack: searchTerm = "' UNION SELECT username, password, NULL FROM users --"
// Result: Extracts all usernames and passwords from users table// SAFE: Using ORM (Sequelize example)
async function searchProducts(searchTerm) {
return await Product.findAll({
where: {
name: {
[Op.like]: `%${searchTerm}%`
}
}
});
}
// Or with prepared statement
function searchProducts(searchTerm) {
const query = 'SELECT * FROM products WHERE name LIKE ?';
return db.execute(query, [`%${searchTerm}%`]);
}Sequelize ORM (Node.js)
// DANGEROUS: Using raw queries with string concatenation
async function getUser(userId) {
const [results] = await sequelize.query(
`SELECT * FROM users WHERE id = ${userId}`
);
return results;
}
// Attack: userId = "1 OR 1=1" exposes all users// SAFE: Using Sequelize model methods
async function getUser(userId) {
return await User.findOne({ where: { id: userId } });
}
// Or with raw queries using replacements
async function getUser(userId) {
const [results] = await sequelize.query(
'SELECT * FROM users WHERE id = :userId',
{
replacements: { userId },
type: QueryTypes.SELECT
}
);
return results[0];
}TypeORM (TypeScript)
// DANGEROUS: String concatenation in query builder
async function searchUsers(searchTerm: string) {
return await dataSource.query(
`SELECT * FROM users WHERE username LIKE '%${searchTerm}%'`
);
}
// Attack: searchTerm = "%'; DROP TABLE users; --"// SAFE: Using TypeORM query builder
async function searchUsers(searchTerm: string) {
return await userRepository
.createQueryBuilder('user')
.where('user.username LIKE :searchTerm', {
searchTerm: `%${searchTerm}%`
})
.getMany();
}
// Or with find options
async function searchUsers(searchTerm: string) {
return await userRepository.find({
where: { username: Like(`%${searchTerm}%`) }
});
}Prisma ORM (TypeScript)
// DANGEROUS: $queryRaw with template literals
async function getProductsByCategory(category: string) {
return await prisma.$queryRaw`
SELECT * FROM products WHERE category = '${category}'
`;
}
// Attack: category = "'; DELETE FROM products; --"// SAFE: Using Prisma Client (automatically parameterized)
async function getProductsByCategory(category: string) {
return await prisma.product.findMany({
where: { category }
});
}
// Or with $queryRaw using Prisma.sql (safe interpolation)
async function getProductsByCategory(category: string) {
return await prisma.$queryRaw`
SELECT * FROM products WHERE category = ${category}
`;
// Prisma automatically parameterizes tagged template values
}Dynamic ORDER BY & Column Names
// DANGEROUS: Dynamic ORDER BY
function getUsers(sortColumn, sortOrder) {
const query = `SELECT * FROM users ORDER BY ${sortColumn} ${sortOrder}`;
return db.execute(query);
}
// Attack: sortColumn = "1; DROP TABLE users; --"
// Could delete the entire users table!// SAFE: Whitelist allowed columns
function getUsers(sortColumn, sortOrder) {
const allowedColumns = ['id', 'name', 'email', 'created_at'];
const allowedOrders = ['ASC', 'DESC'];
if (!allowedColumns.includes(sortColumn)) {
throw new Error('Invalid sort column');
}
if (!allowedOrders.includes(sortOrder.toUpperCase())) {
throw new Error('Invalid sort order');
}
// Now safe to use in query (column name can't be parameterized)
const query = `SELECT * FROM users ORDER BY ${sortColumn} ${sortOrder}`;
return db.execute(query);
}Prevention Methods
Parameterized Queries
CriticalAlways use ? or $1 placeholders instead of string concatenation
ORM/Query Builders
RecommendedUse Sequelize, TypeORM, Prisma, or similar tools that handle escaping
Input Validation
CriticalValidate and sanitize all user inputs before use
Whitelist Approach
CriticalFor dynamic parts (ORDER BY, column names), use whitelisting
Least Privilege
HighDatabase users should have minimal required permissions
Stored Procedures
MediumUse stored procedures with parameterized inputs
WAF Protection
MediumDeploy Web Application Firewall to detect SQL injection attempts
Regular Audits
HighScan code regularly for SQL injection vulnerabilities
Real-World Impact
MOVEit Transfer SQL injection affected 2,000+ organizations
Impact: 77M+ records compromised
Cockroach Labs SQL injection in their cloud platform
Impact: Access to customer data
Capital One breach via SQL injection
Impact: 100M+ customer records stolen
How CodeRaptor Detects SQL Injection
CodeRaptor automatically scans for SQL injection vulnerabilities in pull requests, identifying dangerous patterns before they reach production.