Introduction
SQL injection is one of the oldest—and still most dangerous—web-app attacks. It happens when an attacker feeds specially crafted input into your SQL queries, tricking the database into revealing or altering data it shouldn’t. In this post, we’ll:
1. See a vulnerable Python example
2. Explore several malicious payloads
3. Show how parameterized queries neutralize them
4. Wrap up with a relaxed, practical note on staying safe
When It Matters
Any time you build SQL by inserting or interpolating user input, you open the door to SQLi. Common spots include:
• Login forms (WHERE username = … AND password = …)
• Search or filter fields
• Data-modification operations (INSERT, UPDATE, DELETE)
Whenever you spot code like this, pause and ask: Could this string be used to smuggle SQL?
1) Vulnerable Example
import sqlite3
def fetch_user_vulnerable(username):
conn = sqlite3.connect('app.db') # OPEN DATABASE
cursor = conn.cursor() # CREATE CURSOR
# ❌ DANGEROUS: unsanitized user input in SQL string
query = f"SELECT * FROM users WHERE username = '{username}';"
cursor.execute(query) # EXECUTE untrusted SQL
results = cursor.fetchall() # RETURN ALL ROWS
conn.close() # CLOSE CONNECTION
return results
If someone passes username = "'; DROP TABLE users; --", the query becomes:
SELECT * FROM users WHERE username = ''; DROP TABLE users; --';
That second statement wipes out your users table!
2) Common Malicious Payloads
1. Tautology attack
' OR '1'='1
– Turns WHERE into always-true, leaking all rows.
2. Union-select data dump
' UNION SELECT credit_card, cvv FROM payments; --
– Appends another SELECT to steal sensitive columns.
3. Stacked queries
'; DROP TABLE orders; --
– Executes multiple statements in one go (if supported).
4. Time-based blind SQLi
' OR IF(substr(password,1,1)='a',sleep(5),0); --
– Forces the DB to pause, letting attackers infer data one bit at a time.
5. Comment injection
admin' --
– Cuts off the rest of your SQL, bypassing checks.
3) Neutralizing SQLi with Parameterized Queries
import sqlite3
def fetch_user_safe(username):
conn = sqlite3.connect('app.db') # OPEN DATABASE
cursor = conn.cursor() # CREATE CURSOR
# ✅ PLACEHOLDER ensures input is treated strictly as data
cursor.execute(
"SELECT * FROM users WHERE username = ?", # SQL with placeholder
(username,) # PARAMETER TUPLE
)
results = cursor.fetchall() # RETURN ALL ROWS
conn.close() # CLOSE CONNECTION
return results
No matter what an attacker feeds in—even "' OR '1'='1" or "; DROP TABLE users; --"—the database engine never mixes it with SQL logic.
4) Final Touches: Staying 100% Safe
• Always use parameterized APIs (e.g. ?, %s, :name)—never string concatenation.
• Validate critical inputs (e.g. enforce username formats).
• Limit database privileges so even a successful injection can’t destroy everything.
• Keep dependencies up to date (some drivers fix subtle injection holes).
Comments (0)