Parameterized Queries
Parameterized Queries
One-liner: A secure coding technique that separates SQL commands from user input, preventing SQL Injection by treating data as data, not executable code.
π― What Is It?
Parameterized queries (also called prepared statements) are the primary defense against SQL Injection. Instead of concatenating user input directly into SQL commands, parameterized queries use placeholders (?, @param, :param) that the database engine treats strictly as data values, never as SQL code.
This separation ensures the SQL interpreter cannot be confusedβit knows exactly what is command and what is data.
π€ Why It Matters
- Eliminates SQL Injection at its root β The #1 defense recommended by OWASP
- Works regardless of input β Even malicious payloads like
' OR 1=1--are treated as literal strings - Required for compliance β PCI-DSS, OWASP Top 10, secure coding standards
- Performance benefits β Prepared statements can be cached and reused by the database
Misconceptions About Defenses
β Input validation alone β Blocklists and allowlists can be bypassed
β String escaping β Complex, error-prone, and insufficient
β WAF only β Can be bypassed; doesn't fix the root cause
β
Parameterized queries β Address the vulnerability at its core
π¬ How It Works
Vulnerable Code (String Concatenation)
Python Example (Vulnerable)
# β VULNERABLE: User input concatenated directly into query
username = request.form['username']
password = request.form['password']
query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
cursor.execute(query)
# Attack: username = "admin'--" bypasses password check!
# Resulting query: SELECT * FROM users WHERE username = 'admin'--' AND password = ''
C# Example (Vulnerable)
// β VULNERABLE: String concatenation
string username = Request.Form["username"];
string password = Request.Form["password"];
string query = "SELECT * FROM Users WHERE Username = '" + username + "' AND Password = '" + password + "'";
SqlCommand command = new SqlCommand(query, connection);
SqlDataReader reader = command.ExecuteReader();
Secure Code (Parameterized Queries)
Python Example (Secure)
# β
SECURE: Parameterized query
username = request.form['username']
password = request.form['password']
query = "SELECT * FROM users WHERE username = ? AND password = ?"
cursor.execute(query, (username, password))
# Attack payload: username = "admin'--"
# Database treats entire string as username literal, no SQL injection possible
C# / .NET with MS SQL (Secure)
// β
SECURE: Parameterized query
string username = Request.Form["username"];
string password = Request.Form["password"];
string query = "SELECT * FROM Users WHERE Username = @username AND Password = @password";
SqlCommand command = new SqlCommand(query, connection);
// Add parameters - user input never touches the query string
command.Parameters.AddWithValue("@username", username);
command.Parameters.AddWithValue("@password", password);
SqlDataReader reader = command.ExecuteReader();
PHP with PDO (Secure)
// β
SECURE: Prepared statement with PDO
$username = $_POST['username'];
$password = $_POST['password'];
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
$stmt->bindParam(':username', $username);
$stmt->bindParam(':password', $password);
$stmt->execute();
$user = $stmt->fetch();
Java with JDBC (Secure)
// β
SECURE: PreparedStatement
String username = request.getParameter("username");
String password = request.getParameter("password");
String query = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement stmt = connection.prepareStatement(query);
stmt.setString(1, username);
stmt.setString(2, password);
ResultSet rs = stmt.executeQuery();
π Parameterization by Language/Framework
| Language/Framework | Placeholder Syntax | Function/Class |
|---|---|---|
| Python (sqlite3) | ? |
cursor.execute(query, params) |
| Python (psycopg2) | %s |
cursor.execute(query, params) |
| C# / .NET | @param |
SqlCommand.Parameters.Add() |
| PHP (PDO) | :param or ? |
$pdo->prepare() |
| Java (JDBC) | ? |
PreparedStatement |
| Node.js (mysql2) | ? |
connection.execute(query, params) |
| Ruby (ActiveRecord) | ? or named |
Model.where("id = ?", id) |
π‘οΈ Detection & Prevention
How to Detect (Code Review)
- Look for string concatenation in SQL queries
- Search for
+,f-strings, template literals near SQL keywords - Flag any direct use of user input in query strings
- Check for ORM misuse (raw SQL strings)
How to Detect (Dynamic Testing)
- Inject SQL payloads:
',",' OR 1=1--,'; DROP TABLE-- - Use Burp Suite Intruder or sqlmap
- Monitor for error messages revealing SQL syntax
- Time-based blind SQLi:
'; WAITFOR DELAY '00:00:05'--
How to Prevent
- Always use parameterized queries β No exceptions for user input
- Use ORMs correctly β Most support parameterization by default
- Code review β Enforce parameterization in secure coding standards
- Static analysis β Tools like SonarQube, Semgrep detect SQL injection patterns
- Training β Educate developers on secure database access
π€ Interview Angles
Common Questions
- "What's the best defense against SQL Injection?"
- "Explain how parameterized queries prevent SQL Injection"
- "What's the difference between parameterized queries and input validation?"
- "Can you still get SQL Injection with an ORM?"
STAR Story
Situation: During a penetration test, discovered SQL Injection in a login form that bypassed authentication using
admin'--.
Task: Demonstrate the vulnerability and provide actionable remediation guidance.
Action: Showed the bypass in the pentest report with HTTP request/response evidence. Provided a code example converting the vulnerable Python code from string concatenation to parameterized queries usingcursor.execute(query, (username, password)). Explained that input validation is defense-in-depth but parameterization is the root fix.
Result: Development team implemented parameterized queries across all database interactions. Follow-up testing found zero SQL injection vulnerabilities. Code examples were added to their secure coding standards.
β Best Practices
- Use parameterization for all dynamic queries, even internal tools
- Never concatenate user input into SQL, even after "sanitization"
- Use ORM query builders when possible (they parameterize automatically)
- Include parameterization examples in code review checklists
- Test with SQL injection payloads in QA/staging
β Common Misconceptions
- "Input validation is enough" β Blocklists can be bypassed; parameterization is the primary control
- "Stored procedures prevent SQL Injection" β Only if they use parameterization internally
- "ORMs are always safe" β
Model.raw(),execute_raw()can still be vulnerable - "Only public-facing apps need parameterization" β Internal apps are targets too
π Related Concepts
π References
- OWASP SQL Injection Prevention Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html
- OWASP Query Parameterization: https://cheatsheetseries.owasp.org/cheatsheets/Query_Parameterization_Cheat_Sheet.html
- CWE-89: SQL Injection: https://cwe.mitre.org/data/definitions/89.html
- Bobby Tables: https://bobby-tables.com/