SQL Injection (SQLi)
Imagine a bank teller who follows instructions literally. If you write on a withdrawal slip “Give me $100; also, show me everyone’s account balance,” a careless teller might do both. SQL injection works the same way: an attacker types malicious database commands into a website’s input fields (like a login form or search box), and if the application is not careful, it runs those commands against the database.
SQL Injection (SQLi) is an injection attack where an adversary inserts or “injects” malicious SQL statements into input fields or parameters that are incorporated into database queries without proper sanitization. It ranks consistently in the OWASP Top 10 (A03:2021 Injection).
Attack types:
- Classic (in-band): the attacker receives the query result directly in the HTTP response. Example:
' OR 1=1 --in a login field bypasses authentication by making the WHERE clause always true. - Union-based: uses
UNION SELECTto append additional queries and extract data from other tables. - Blind SQLi: no visible error or data in the response. The attacker infers information by observing response time (time-based) or subtle differences in page content (boolean-based).
- Second-order: malicious input is stored in the database and executed later when used in a different query.
Vulnerable code pattern (Node.js):
// DANGEROUS: string concatenation
const query = `SELECT * FROM users WHERE email = '${userInput}'`;Secure pattern (parameterized query):
// SAFE: parameterized query
const query = 'SELECT * FROM users WHERE email = $1';
const result = await db.query(query, [userInput]);Prevention:
- Parameterized queries / prepared statements (primary defense)
- ORM usage with proper escaping (Prisma, Sequelize, SQLAlchemy)
- Input validation (allowlisting expected formats)
- WAF rules (secondary defense, not sufficient alone)
- Least privilege database accounts (limit what the app can access)
SQL injection demonstration
-- Vulnerable query: SELECT * FROM users WHERE username = '{input}'
-- Attacker input: ' OR '1'='1' --
-- Resulting query:
SELECT * FROM users WHERE username = '' OR '1'='1' --'
-- Returns ALL rows (authentication bypass)
-- Union-based extraction:
-- Attacker input: ' UNION SELECT username, password FROM users --
-- Resulting query:
SELECT name, price FROM products WHERE id = '' UNION SELECT username, password FROM users --'
-- Dumps the user credentials table
-- Parameterized query (safe):
-- Attacker input treated as literal string, not SQL code
SELECT * FROM users WHERE username = $1 -- $1 = "' OR '1'='1' --"
-- Returns zero rows (attack neutralized) SQL injection remains one of the most exploited vulnerabilities despite being well understood and entirely preventable. The 2017 Equifax breach that exposed 147 million records involved an unpatched Apache Struts vulnerability that enabled injection. Modern frameworks (Rails, Django, Laravel, Next.js with Prisma) use parameterized queries by default, but raw SQL queries in custom code, stored procedures, and legacy systems remain common attack surfaces. Security scanners like SQLMap automate the detection and exploitation of SQLi. Every penetration test includes SQLi testing, and PCI-DSS requires protection against injection attacks for any system handling payment data.