Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
286: Executing Parameterized Queries Safely
I've spent a few too many late nights auditing legacy codebases where I found developers trying to "clean" their input before sticking it into a SQL query. There is a very persistent belief that if you just strip out single quotes or use a `.replace()` method to escape problematic characters, you've effectively neutralized the threat of SQL injection. It feels intuitive—you're just removing the "bad" parts, right?
Thinking "Manual Escaping" is a Security Strategy
Let's look at a piece of code that looks "safe" to a beginner. Imagine you're building a user profile search and you decide to be proactive about security:
# The "I'll just clean the string" approach
username = "admin' --" # Malicious input
safe_username = username.replace("'", "''") # Escaping the quote
query = f"SELECT * FROM users WHERE username = '{safe_username}'"
# Result: SELECT * FROM users WHERE username = 'admin'' --'
In this specific case, the replace method might actually stop the most basic attack. But here is the problem: you are playing a game of whack-a-mole. Between different character encodings, null bytes, and the specific quirks of different SQL dialects (PostgreSQL vs. MySQL vs. SQLite), there is almost always a way to bypass a manual filter. You're trying to predict every possible way a human can be clever with a string. I can tell you from experience: the hackers are more creative than your .replace() chain.
Letting the Database Driver Handle the Heavy Lifting
The real solution isn't to clean the data; it's to stop treating the data as part of the command. This is where parameterized queries come in. Instead of building a string and sending it to the database, you send a template and a separate list of values.
When you use parameterization, the database driver sends the SQL command and the data in two different packets, or it handles the binding at a protocol level. The database engine receives the query and knows exactly which parts are logic and which parts are just literal values. Even if the user enters ' OR 1=1 --, the database just looks for a user whose literal name is the string ' OR 1=1 --. It never executes that string as code.
Here is how you actually do this using a standard Python DB-API 2.0 compatible driver (like sqlite3 or psycopg2):
import sqlite3
connection = sqlite3.connect("app.db")
cursor = connection.cursor()
# The user input we don't trust
user_id = "123"
# CORRECT: Use a placeholder (?) and pass values as a tuple
# Note: No f-strings, no % formatting, no .format()
cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
result = cursor.fetchone()
A few quick tips on the syntax: notice that the second argument to .execute() is a tuple (user_id,). The trailing comma is mandatory for single-element tuples in Python. If you forget it, Python sees it as parentheses around a string, and the driver will try to iterate over every character in your string as a separate parameter, leading to a confusing "Wrong number of bindings" error.
Whether you use ? (SQLite), %s (PostgreSQL/MySQL), or :name (Oracle), the principle is the same: keep your logic and your data in separate lanes. Never, ever let them merge before they hit the database.
📋 Practical Task
Securing the Product Inventory Lookup
You've been handed a piece of a warehouse management system that is dangerously vulnerable to SQL injection. The current code uses an f-string to build a query for looking up products by their SKU (Stock Keeping Unit).
Your Task: Rewrite the get_product_details function. You must remove the f-string and implement a safe, parameterized query using the provided cursor.execute() method. Ensure that the input sku is passed as a parameter to the driver rather than being interpolated into the string.
import sqlite3
def get_product_details(cursor, sku):
# VULNERABLE CODE:
# query = f"SELECT name, price, stock FROM products WHERE sku = '{sku}'"
# cursor.execute(query)
# TODO: Implement the safe version here
pass
# Test Setup
conn = sqlite3.connect(":memory:")
cur = conn.cursor()
cur.execute("CREATE TABLE products (sku TEXT, name TEXT, price REAL, stock INTEGER)")
cur.execute("INSERT INTO products VALUES ('ABC-123', 'Mechanical Keyboard', 120.00, 15)")
conn.commit()
# This should work normally
print(get_product_details(cur, 'ABC-123'))
# This malicious input should now return None instead of crashing or leaking data
print(get_product_details(cur, " ' OR '1'='1 "))
There are no comments for now.