SQL injection and command injection: how they work and how to stop them
SQL injection anatomy, parameterized queries, prepared statements, ORM protection, command injection, input validation, allowlist vs blocklist, error message information leak
Injection Attacks
Injection is the most critical web vulnerability class. An attacker supplies input that is interpreted as code instead of data. If your application constructs queries by concatenating user input, you are vulnerable.
SQL injection
# Vulnerable — user input concatenated directly
query = f"SELECT * FROM users WHERE email = '{email}'"
# Input: ' OR '1'='1 turns this into:
SELECT * FROM users WHERE email = '' OR '1'='1'
# Returns all users
# Attack that drops a table:
email = "'; DROP TABLE users; --"
# Fixed — parameterized query (prepared statement)
cursor.execute("SELECT * FROM users WHERE email = %s", (email,))
# The database treats the input as a literal string, never as SQLCommand injection
# Vulnerable — passes user input to shell
import subprocess
subprocess.run(f"ping {hostname}", shell=True)
# Input: 8.8.8.8; rm -rf /
# Fixed — pass arguments as a list (no shell interpolation)
subprocess.run(["ping", "-c", "1", hostname])Defense principles
Always use parameterized queries or prepared statements — never concatenate. Use an ORM that parameterizes by default. Apply allowlist validation for inputs with known formats (IP addresses, file extensions). Never expose raw database errors to users — they leak schema information to attackers. Log errors server-side and return generic error messages to clients.
