SQL injection has been on the OWASP Top 10 for over 20 years. It's still there. Let that sink in.
You concatenate user input into a SQL query. The attacker puts SQL code in the input. Your database executes it.
# ❌ The dangerous pattern
user_input = request.args.get('id')
query = f"SELECT * FROM users WHERE id = {user_input}"
# Attacker sends: id=1 OR 1=1
# Query becomes: SELECT * FROM users WHERE id = 1 OR 1=1
# Returns: ALL USERS in the database-- Attacker input:
1 UNION SELECT username, password, null FROM users--
-- Full query:
SELECT * FROM products WHERE id = 1
UNION SELECT username, password, null FROM users--The attacker gets your entire users table.
-- Login query:
SELECT * FROM users WHERE username='{input}' AND password='{input}'
-- Attacker username:
admin'--
-- Result:
SELECT * FROM users WHERE username='admin'--' AND password='...'
-- Everything after -- is a comment. Password check skipped. Admin access granted.# ✅ Parameterized queries — ALWAYS do this
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
# ✅ ORM (handles parameterization for you)
User.objects.filter(id=user_id)
# ✅ Stored procedures (when done right)
cursor.callproc('get_user', [user_id])# sqlmap — automated SQL injection testing
pip install sqlmap
sqlmap -u "https://yourapp.com/api/user?id=1" --dbsIf sqlmap finds something, fix it before someone else does.