+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 192 of 365

๐Ÿ“˜ Security: SQL Injection Prevention

Master security: sql injection prevention in Python with practical examples, best practices, and real-world applications ๐Ÿš€

๐Ÿ’ŽAdvanced
25 min read

Prerequisites

  • Basic understanding of programming concepts ๐Ÿ“
  • Python installation (3.8+) ๐Ÿ
  • VS Code or preferred IDE ๐Ÿ’ป

What you'll learn

  • Understand the concept fundamentals ๐ŸŽฏ
  • Apply the concept in real projects ๐Ÿ—๏ธ
  • Debug common issues ๐Ÿ›
  • Write clean, Pythonic code โœจ

๐ŸŽฏ Introduction

Welcome to this crucial tutorial on SQL injection prevention! ๐ŸŽ‰ In this guide, weโ€™ll explore how to protect your Python applications from one of the most dangerous security vulnerabilities out there.

Youโ€™ll discover how SQL injection attacks work and, more importantly, how to stop them cold! Whether youโ€™re building web applications ๐ŸŒ, APIs ๐Ÿ–ฅ๏ธ, or database-driven systems ๐Ÿ“Š, understanding SQL injection prevention is essential for writing secure, professional code.

By the end of this tutorial, youโ€™ll be a security champion, ready to defend your databases from malicious attacks! Letโ€™s dive in! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding SQL Injection

๐Ÿค” What is SQL Injection?

SQL injection is like leaving your house key under the doormat with a big sign saying โ€œKEY HERE!โ€ ๐Ÿ—๏ธ. Think of it as allowing strangers to slip their own commands into your database queries, potentially stealing data or wreaking havoc.

In Python terms, SQL injection happens when user input is directly concatenated into SQL queries without proper sanitization. This means attackers can:

  • ๐Ÿ”“ Bypass authentication systems
  • ๐Ÿ“Š Steal sensitive data
  • ๐Ÿ’ฃ Delete or modify database records
  • ๐ŸŽญ Impersonate other users

๐Ÿ’ก Why Prevent SQL Injection?

Hereโ€™s why developers must prioritize SQL injection prevention:

  1. Data Protection ๐Ÿ”’: Keep sensitive information safe
  2. Legal Compliance ๐Ÿ“œ: Meet security regulations (GDPR, HIPAA)
  3. Reputation ๐ŸŒŸ: Maintain user trust and confidence
  4. Financial Safety ๐Ÿ’ฐ: Avoid costly breaches and fines

Real-world example: Imagine building an online store ๐Ÿ›’. Without SQL injection prevention, a hacker could view all customer credit cards, change prices to $0.01, or delete your entire inventory!

๐Ÿ”ง Basic Syntax and Usage

๐Ÿ“ The Dangerous Way (Never Do This!)

Letโ€™s start by seeing what NOT to do:

# โŒ NEVER DO THIS - Vulnerable to SQL injection!
def bad_login(username, password):
    # ๐Ÿ’ฅ Direct string concatenation = disaster!
    query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
    cursor.execute(query)
    return cursor.fetchone()

# ๐Ÿ˜ˆ Attacker could input:
# username: admin' --
# This makes the query: SELECT * FROM users WHERE username = 'admin' --' AND password = ''
# The -- comments out the password check!

๐Ÿ’ก Explanation: See how dangerous that is? The attacker bypassed the password check entirely! ๐Ÿ˜ฑ

๐ŸŽฏ Safe Patterns with Parameterized Queries

Hereโ€™s how to do it safely:

import sqlite3
import psycopg2  # For PostgreSQL
import mysql.connector  # For MySQL

# โœ… Safe way - Using parameterized queries
def safe_login_sqlite(username, password):
    # ๐Ÿ›ก๏ธ Use ? placeholders for SQLite
    query = "SELECT * FROM users WHERE username = ? AND password = ?"
    cursor.execute(query, (username, password))
    return cursor.fetchone()

# โœ… PostgreSQL style
def safe_login_postgres(username, password):
    # ๐Ÿ›ก๏ธ Use %s placeholders for PostgreSQL
    query = "SELECT * FROM users WHERE username = %s AND password = %s"
    cursor.execute(query, (username, password))
    return cursor.fetchone()

# โœ… MySQL style  
def safe_login_mysql(username, password):
    # ๐Ÿ›ก๏ธ Use %s placeholders for MySQL too
    query = "SELECT * FROM users WHERE username = %s AND password = %s"
    cursor.execute(query, (username, password))
    return cursor.fetchone()

๐Ÿ’ก Practical Examples

Letโ€™s build a safe product search:

import sqlite3
from typing import List, Dict, Optional

class SecureProductSearch:
    def __init__(self, db_path: str):
        self.conn = sqlite3.connect(db_path)
        self.conn.row_factory = sqlite3.Row  # ๐Ÿ“Š Get dict-like results
        
    # โœ… Safe search with multiple parameters
    def search_products(self, 
                       name: Optional[str] = None,
                       min_price: Optional[float] = None,
                       max_price: Optional[float] = None,
                       category: Optional[str] = None) -> List[Dict]:
        # ๐Ÿ—๏ธ Build query dynamically but safely!
        query_parts = ["SELECT * FROM products WHERE 1=1"]
        params = []
        
        if name:
            query_parts.append("AND name LIKE ?")
            params.append(f"%{name}%")  # ๐Ÿ” Safe wildcards
            
        if min_price is not None:
            query_parts.append("AND price >= ?")
            params.append(min_price)
            
        if max_price is not None:
            query_parts.append("AND price <= ?")
            params.append(max_price)
            
        if category:
            query_parts.append("AND category = ?")
            params.append(category)
            
        # ๐ŸŽฏ Join query parts safely
        query = " ".join(query_parts)
        cursor = self.conn.execute(query, params)
        
        # ๐Ÿ“ฆ Return results as list of dicts
        return [dict(row) for row in cursor.fetchall()]
    
    # โœ… Safe product insertion
    def add_product(self, name: str, price: float, category: str, emoji: str):
        query = """
        INSERT INTO products (name, price, category, emoji) 
        VALUES (?, ?, ?, ?)
        """
        try:
            self.conn.execute(query, (name, price, category, emoji))
            self.conn.commit()
            print(f"โœ… Added {emoji} {name} to inventory!")
        except sqlite3.IntegrityError:
            print(f"โš ๏ธ Product {name} already exists!")

# ๐ŸŽฎ Let's use it!
shop = SecureProductSearch("shop.db")

# ๐Ÿ” Safe searches
gaming_products = shop.search_products(category="Gaming", max_price=60)
cheap_finds = shop.search_products(min_price=10, max_price=30)

# โž• Safe product addition
shop.add_product("Python Book", 29.99, "Education", "๐Ÿ“˜")
shop.add_product("Gaming Mouse", 49.99, "Gaming", "๐Ÿ–ฑ๏ธ")

๐ŸŽฏ Try it yourself: Add a method to safely update product prices with validation!

๐ŸŽฎ Example 2: Secure User Management System

Letโ€™s create a bulletproof user system:

import hashlib
import secrets
from datetime import datetime
from typing import Optional, Dict

class SecureUserManager:
    def __init__(self, connection):
        self.conn = connection
        self.cursor = connection.cursor()
        
    # ๐Ÿ” Hash passwords securely
    def _hash_password(self, password: str, salt: str) -> str:
        # ๐Ÿ›ก๏ธ Use strong hashing
        return hashlib.pbkdf2_hmac(
            'sha256',
            password.encode('utf-8'),
            salt.encode('utf-8'),
            100000  # ๐Ÿ”„ 100k iterations
        ).hex()
    
    # โœ… Safe user registration
    def register_user(self, username: str, email: str, password: str) -> bool:
        # ๐ŸŽฒ Generate random salt
        salt = secrets.token_hex(32)
        password_hash = self._hash_password(password, salt)
        
        try:
            # ๐Ÿ›ก๏ธ Parameterized query prevents injection
            query = """
            INSERT INTO users (username, email, password_hash, salt, created_at, emoji)
            VALUES (?, ?, ?, ?, ?, ?)
            """
            self.cursor.execute(query, (
                username, 
                email, 
                password_hash, 
                salt, 
                datetime.now(),
                "๐Ÿง‘โ€๐Ÿ’ป"  # Default user emoji!
            ))
            self.conn.commit()
            print(f"๐ŸŽ‰ Welcome aboard, {username}!")
            return True
            
        except sqlite3.IntegrityError:
            print(f"โŒ Username {username} already taken!")
            return False
    
    # โœ… Safe authentication
    def authenticate(self, username: str, password: str) -> Optional[Dict]:
        # ๐Ÿ” Safely fetch user
        query = "SELECT * FROM users WHERE username = ?"
        self.cursor.execute(query, (username,))
        user = self.cursor.fetchone()
        
        if not user:
            print("โŒ User not found!")
            return None
            
        # ๐Ÿ” Verify password
        password_hash = self._hash_password(password, user['salt'])
        if password_hash == user['password_hash']:
            print(f"โœ… Welcome back, {username}! {user['emoji']}")
            # ๐Ÿ“ Update last login safely
            update_query = "UPDATE users SET last_login = ? WHERE id = ?"
            self.cursor.execute(update_query, (datetime.now(), user['id']))
            self.conn.commit()
            return dict(user)
        else:
            print("โŒ Invalid password!")
            return None
    
    # โœ… Safe privilege checking
    def check_admin(self, user_id: int) -> bool:
        # ๐Ÿ›ก๏ธ Never trust client-side role checks!
        query = "SELECT is_admin FROM users WHERE id = ?"
        self.cursor.execute(query, (user_id,))
        result = self.cursor.fetchone()
        return bool(result and result['is_admin'])
    
    # โœ… Safe user search with protection
    def search_users(self, search_term: str) -> List[Dict]:
        # ๐Ÿ” Escape special characters for LIKE
        # But still use parameterized queries!
        safe_term = search_term.replace('%', '\\%').replace('_', '\\_')
        
        query = """
        SELECT id, username, email, emoji, created_at 
        FROM users 
        WHERE username LIKE ? ESCAPE '\\'
        OR email LIKE ? ESCAPE '\\'
        """
        
        search_pattern = f"%{safe_term}%"
        self.cursor.execute(query, (search_pattern, search_pattern))
        
        return [dict(row) for row in self.cursor.fetchall()]

# ๐ŸŽฎ Test our secure system!
user_mgr = SecureUserManager(connection)

# ๐Ÿ‘ค Register safely
user_mgr.register_user("alice", "[email protected]", "StrongPass123!")
user_mgr.register_user("bob", "[email protected]", "SecurePass456!")

# ๐Ÿ” Authenticate safely
alice = user_mgr.authenticate("alice", "StrongPass123!")

# ๐Ÿ” Search safely (even with injection attempts!)
results = user_mgr.search_users("alice' OR '1'='1")  # ๐Ÿ›ก๏ธ This is now safe!

๐Ÿš€ Advanced Concepts

๐Ÿง™โ€โ™‚๏ธ Advanced Defense: Stored Procedures

For ultimate security, use stored procedures:

# ๐ŸŽฏ Using stored procedures (MySQL example)
class UltraSecureDB:
    def __init__(self, connection):
        self.conn = connection
        self.cursor = connection.cursor()
        
    # ๐Ÿ—๏ธ Create secure stored procedures
    def setup_procedures(self):
        # ๐Ÿ›ก๏ธ Procedure for user login
        self.cursor.execute("""
        CREATE PROCEDURE SecureLogin(
            IN p_username VARCHAR(255),
            IN p_password_hash VARCHAR(255)
        )
        BEGIN
            SELECT id, username, email, is_admin 
            FROM users 
            WHERE username = p_username 
            AND password_hash = p_password_hash;
        END
        """)
        
        # ๐Ÿ›ก๏ธ Procedure for adding products
        self.cursor.execute("""
        CREATE PROCEDURE AddProduct(
            IN p_name VARCHAR(255),
            IN p_price DECIMAL(10,2),
            IN p_category VARCHAR(100)
        )
        BEGIN
            INSERT INTO products (name, price, category)
            VALUES (p_name, p_price, p_category);
        END
        """)
    
    # โœ… Call procedures safely
    def login_with_procedure(self, username: str, password_hash: str):
        # ๐Ÿš€ Even safer than parameterized queries!
        self.cursor.callproc('SecureLogin', [username, password_hash])
        return self.cursor.fetchone()

๐Ÿ—๏ธ Advanced Pattern: Query Builder with Validation

Create a query builder that validates everything:

from enum import Enum
from typing import List, Any, Tuple

class SQLOperator(Enum):
    EQUALS = "="
    LIKE = "LIKE"
    GREATER = ">"
    LESS = "<"
    IN = "IN"

class SecureQueryBuilder:
    def __init__(self, table: str):
        # ๐Ÿ›ก๏ธ Validate table name against whitelist
        allowed_tables = ['users', 'products', 'orders', 'categories']
        if table not in allowed_tables:
            raise ValueError(f"โš ๏ธ Invalid table: {table}")
            
        self.table = table
        self.conditions = []
        self.params = []
        
    # โœ… Add WHERE conditions safely
    def where(self, column: str, operator: SQLOperator, value: Any):
        # ๐Ÿ›ก๏ธ Validate column name
        allowed_columns = {
            'users': ['id', 'username', 'email', 'created_at'],
            'products': ['id', 'name', 'price', 'category']
        }
        
        if column not in allowed_columns.get(self.table, []):
            raise ValueError(f"โš ๏ธ Invalid column: {column}")
            
        # ๐Ÿ—๏ธ Build condition safely
        if operator == SQLOperator.IN:
            placeholders = ', '.join(['?' for _ in value])
            self.conditions.append(f"{column} IN ({placeholders})")
            self.params.extend(value)
        else:
            self.conditions.append(f"{column} {operator.value} ?")
            self.params.append(value)
            
        return self  # ๐Ÿ”„ Allow chaining
    
    # โœ… Build final query
    def build(self) -> Tuple[str, List[Any]]:
        query = f"SELECT * FROM {self.table}"
        
        if self.conditions:
            query += " WHERE " + " AND ".join(self.conditions)
            
        return query, self.params
    
    # ๐ŸŽฏ Execute safely
    def execute(self, cursor):
        query, params = self.build()
        cursor.execute(query, params)
        return cursor.fetchall()

# ๐ŸŽฎ Use the secure builder
builder = SecureQueryBuilder('products')
results = builder.where('category', SQLOperator.EQUALS, 'Gaming') \
                .where('price', SQLOperator.LESS, 100) \
                .execute(cursor)

print(f"๐ŸŽฎ Found {len(results)} gaming products under $100!")

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: String Formatting with User Input

# โŒ NEVER use string formatting with user input!
username = "admin'; DROP TABLE users; --"
query = f"SELECT * FROM users WHERE name = '{username}'"  # ๐Ÿ’ฅ Table deleted!

# โŒ Also dangerous - % formatting
query = "SELECT * FROM users WHERE name = '%s'" % username  # ๐Ÿ’ฅ Still vulnerable!

# โŒ .format() is not safe either!
query = "SELECT * FROM users WHERE name = '{}'".format(username)  # ๐Ÿ’ฅ Nope!

# โœ… ALWAYS use parameterized queries
query = "SELECT * FROM users WHERE name = ?"
cursor.execute(query, (username,))  # ๐Ÿ›ก๏ธ Safe!

๐Ÿคฏ Pitfall 2: Dynamic Table/Column Names

# โŒ Can't use parameters for table/column names
table = "users"  # What if this comes from user input?
query = "SELECT * FROM ?"  # ๐Ÿšซ This won't work!

# โœ… Use a whitelist approach
ALLOWED_TABLES = {'users', 'products', 'orders'}

def safe_query_table(table_name: str, condition: str, value: str):
    # ๐Ÿ›ก๏ธ Validate against whitelist
    if table_name not in ALLOWED_TABLES:
        raise ValueError(f"Invalid table: {table_name}")
    
    # โœ… Now safe to use in query
    query = f"SELECT * FROM {table_name} WHERE status = ?"
    return cursor.execute(query, (value,))

๐Ÿ’ฃ Pitfall 3: Trusting Numeric Input

# โŒ Numbers can be dangerous too!
user_id = request.args.get('id')  # Could be: "1 OR 1=1"
query = f"SELECT * FROM users WHERE id = {user_id}"  # ๐Ÿ’ฅ Returns all users!

# โœ… Always parameterize, even for numbers
def get_user_by_id(user_id: str):
    try:
        # ๐Ÿ”ข Validate it's actually a number
        user_id_int = int(user_id)
        query = "SELECT * FROM users WHERE id = ?"
        cursor.execute(query, (user_id_int,))
        return cursor.fetchone()
    except ValueError:
        print("โŒ Invalid user ID!")
        return None

๐Ÿ› ๏ธ Best Practices

  1. ๐ŸŽฏ Always Use Parameterized Queries: No exceptions, ever!
  2. ๐Ÿ“ Validate All Input: Check types, lengths, and patterns
  3. ๐Ÿ›ก๏ธ Use Least Privilege: Database users should have minimal permissions
  4. ๐ŸŽจ Use an ORM: SQLAlchemy, Django ORM provide built-in protection
  5. โœจ Regular Security Audits: Test your code with tools like sqlmap
  6. ๐Ÿ” Hash Passwords Properly: Never store plain text passwords
  7. ๐Ÿ“Š Log Suspicious Activity: Track failed login attempts
  8. ๐Ÿš€ Keep Dependencies Updated: Security patches matter!

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Secure Blog System

Create a SQL-injection-proof blog platform:

๐Ÿ“‹ Requirements:

  • โœ… User registration and login system
  • ๐Ÿ“ Create, edit, and delete blog posts
  • ๐Ÿ’ฌ Comment system with moderation
  • ๐Ÿ” Search functionality (by title, content, author)
  • ๐Ÿท๏ธ Tag system for categorizing posts
  • ๐Ÿ‘ค User roles (admin, author, reader)
  • ๐Ÿ“Š View counting without allowing manipulation

๐Ÿš€ Bonus Points:

  • Add rate limiting for login attempts
  • Implement CSRF protection
  • Create an audit log for all database changes
  • Add input sanitization for XSS prevention

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
import sqlite3
import hashlib
import secrets
from datetime import datetime, timedelta
from typing import Optional, List, Dict
import re

class SecureBlogSystem:
    def __init__(self, db_path: str):
        self.conn = sqlite3.connect(db_path)
        self.conn.row_factory = sqlite3.Row
        self.setup_database()
        self.login_attempts = {}  # ๐Ÿ”’ Track failed logins
        
    def setup_database(self):
        # ๐Ÿ—๏ธ Create secure schema
        self.conn.executescript("""
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY,
            username TEXT UNIQUE NOT NULL,
            email TEXT UNIQUE NOT NULL,
            password_hash TEXT NOT NULL,
            salt TEXT NOT NULL,
            role TEXT DEFAULT 'reader',
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            is_active BOOLEAN DEFAULT 1
        );
        
        CREATE TABLE IF NOT EXISTS posts (
            id INTEGER PRIMARY KEY,
            author_id INTEGER NOT NULL,
            title TEXT NOT NULL,
            content TEXT NOT NULL,
            status TEXT DEFAULT 'draft',
            view_count INTEGER DEFAULT 0,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (author_id) REFERENCES users(id)
        );
        
        CREATE TABLE IF NOT EXISTS comments (
            id INTEGER PRIMARY KEY,
            post_id INTEGER NOT NULL,
            user_id INTEGER NOT NULL,
            content TEXT NOT NULL,
            is_approved BOOLEAN DEFAULT 0,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (post_id) REFERENCES posts(id),
            FOREIGN KEY (user_id) REFERENCES users(id)
        );
        
        CREATE TABLE IF NOT EXISTS tags (
            id INTEGER PRIMARY KEY,
            name TEXT UNIQUE NOT NULL
        );
        
        CREATE TABLE IF NOT EXISTS post_tags (
            post_id INTEGER NOT NULL,
            tag_id INTEGER NOT NULL,
            PRIMARY KEY (post_id, tag_id),
            FOREIGN KEY (post_id) REFERENCES posts(id),
            FOREIGN KEY (tag_id) REFERENCES tags(id)
        );
        
        CREATE TABLE IF NOT EXISTS audit_log (
            id INTEGER PRIMARY KEY,
            user_id INTEGER,
            action TEXT NOT NULL,
            table_name TEXT NOT NULL,
            record_id INTEGER,
            timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (user_id) REFERENCES users(id)
        );
        """)
        
    # ๐Ÿ” Secure authentication with rate limiting
    def login(self, username: str, password: str) -> Optional[Dict]:
        # ๐Ÿ›ก๏ธ Check rate limiting
        if self._is_rate_limited(username):
            print("โš ๏ธ Too many login attempts. Please wait.")
            return None
            
        # ๐Ÿ” Fetch user safely
        query = "SELECT * FROM users WHERE username = ? AND is_active = 1"
        cursor = self.conn.execute(query, (username,))
        user = cursor.fetchone()
        
        if not user:
            self._record_failed_login(username)
            return None
            
        # ๐Ÿ” Verify password
        password_hash = hashlib.pbkdf2_hmac(
            'sha256', 
            password.encode(), 
            user['salt'].encode(), 
            100000
        ).hex()
        
        if password_hash == user['password_hash']:
            # โœ… Success - clear failed attempts
            self.login_attempts.pop(username, None)
            self._audit_log(user['id'], 'LOGIN', 'users', user['id'])
            return dict(user)
        else:
            self._record_failed_login(username)
            return None
    
    # ๐Ÿ›ก๏ธ Rate limiting helper
    def _is_rate_limited(self, username: str) -> bool:
        if username not in self.login_attempts:
            return False
            
        attempts = self.login_attempts[username]
        if len(attempts) >= 5:
            # ๐Ÿ• Check if 15 minutes passed since last attempt
            last_attempt = attempts[-1]
            if datetime.now() - last_attempt < timedelta(minutes=15):
                return True
                
        return False
    
    def _record_failed_login(self, username: str):
        if username not in self.login_attempts:
            self.login_attempts[username] = []
        self.login_attempts[username].append(datetime.now())
        
    # ๐Ÿ“ Create post with XSS prevention
    def create_post(self, user_id: int, title: str, content: str, tags: List[str]) -> int:
        # ๐Ÿ›ก๏ธ Validate user permissions
        if not self._can_create_post(user_id):
            raise PermissionError("You cannot create posts!")
            
        # ๐Ÿงน Basic XSS prevention (in real app, use bleach library)
        safe_title = self._sanitize_html(title)
        safe_content = self._sanitize_html(content)
        
        # โœ… Insert post safely
        query = """
        INSERT INTO posts (author_id, title, content, status)
        VALUES (?, ?, ?, 'published')
        """
        cursor = self.conn.execute(query, (user_id, safe_title, safe_content))
        post_id = cursor.lastrowid
        
        # ๐Ÿท๏ธ Add tags safely
        for tag_name in tags:
            tag_id = self._get_or_create_tag(tag_name)
            self.conn.execute(
                "INSERT INTO post_tags (post_id, tag_id) VALUES (?, ?)",
                (post_id, tag_id)
            )
            
        self.conn.commit()
        self._audit_log(user_id, 'CREATE', 'posts', post_id)
        
        print(f"โœ… Post created successfully! ID: {post_id}")
        return post_id
    
    # ๐Ÿงน Basic HTML sanitization
    def _sanitize_html(self, text: str) -> str:
        # Remove script tags and javascript:
        text = re.sub(r'<script[^>]*>.*?</script>', '', text, flags=re.DOTALL | re.IGNORECASE)
        text = re.sub(r'javascript:', '', text, flags=re.IGNORECASE)
        text = re.sub(r'on\w+\s*=', '', text)  # Remove event handlers
        return text
    
    # ๐Ÿ” Secure search with highlighting
    def search_posts(self, query: str, author: Optional[str] = None) -> List[Dict]:
        # ๐Ÿ›ก๏ธ Sanitize search query
        safe_query = f"%{query}%"
        
        sql = """
        SELECT p.*, u.username as author_name
        FROM posts p
        JOIN users u ON p.author_id = u.id
        WHERE p.status = 'published'
        AND (p.title LIKE ? OR p.content LIKE ?)
        """
        
        params = [safe_query, safe_query]
        
        if author:
            sql += " AND u.username = ?"
            params.append(author)
            
        sql += " ORDER BY p.created_at DESC"
        
        cursor = self.conn.execute(sql, params)
        return [dict(row) for row in cursor.fetchall()]
    
    # ๐Ÿ“Š Increment view count safely
    def increment_view_count(self, post_id: int):
        # ๐Ÿ›ก๏ธ Atomic increment to prevent race conditions
        query = "UPDATE posts SET view_count = view_count + 1 WHERE id = ?"
        self.conn.execute(query, (post_id,))
        self.conn.commit()
    
    # ๐Ÿ” Check permissions
    def _can_create_post(self, user_id: int) -> bool:
        query = "SELECT role FROM users WHERE id = ?"
        cursor = self.conn.execute(query, (user_id,))
        user = cursor.fetchone()
        return user and user['role'] in ['admin', 'author']
    
    # ๐Ÿ“Š Audit logging
    def _audit_log(self, user_id: int, action: str, table: str, record_id: int):
        query = """
        INSERT INTO audit_log (user_id, action, table_name, record_id)
        VALUES (?, ?, ?, ?)
        """
        self.conn.execute(query, (user_id, action, table, record_id))
        self.conn.commit()
    
    # ๐Ÿท๏ธ Tag management
    def _get_or_create_tag(self, tag_name: str) -> int:
        # ๐Ÿงน Clean tag name
        clean_tag = tag_name.strip().lower()
        
        # ๐Ÿ” Check if exists
        cursor = self.conn.execute(
            "SELECT id FROM tags WHERE name = ?", 
            (clean_tag,)
        )
        tag = cursor.fetchone()
        
        if tag:
            return tag['id']
        else:
            # โœจ Create new tag
            cursor = self.conn.execute(
                "INSERT INTO tags (name) VALUES (?)",
                (clean_tag,)
            )
            return cursor.lastrowid

# ๐ŸŽฎ Test the secure blog system!
blog = SecureBlogSystem("blog.db")

# ๐Ÿ‘ค Login safely (after creating users)
user = blog.login("alice", "SecurePass123!")

if user:
    # ๐Ÿ“ Create a post
    post_id = blog.create_post(
        user['id'],
        "SQL Injection Prevention Guide",
        "Here's how to keep your database safe...",
        ["security", "python", "sql"]
    )
    
    # ๐Ÿ” Search safely
    results = blog.search_posts("injection")
    print(f"๐Ÿ” Found {len(results)} posts about injection")
    
    # ๐Ÿ“Š Track views
    blog.increment_view_count(post_id)

๐ŸŽ“ Key Takeaways

Youโ€™ve learned so much! Hereโ€™s what you can now do:

  • โœ… Identify SQL injection vulnerabilities with confidence ๐Ÿ’ช
  • โœ… Write secure database queries using parameterization ๐Ÿ›ก๏ธ
  • โœ… Validate and sanitize all user input ๐Ÿงน
  • โœ… Implement defense in depth with multiple security layers ๐Ÿฐ
  • โœ… Build secure applications that protect user data! ๐Ÿš€

Remember: Security isnโ€™t optional - itโ€™s essential! Every query you write should be injection-proof. ๐Ÿค

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve mastered SQL injection prevention!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Practice with the blog system exercise above
  2. ๐Ÿ—๏ธ Audit your existing projects for SQL injection vulnerabilities
  3. ๐Ÿ“š Learn about other security topics: XSS, CSRF, authentication
  4. ๐ŸŒŸ Share your security knowledge with other developers!

Remember: Youโ€™re now a guardian of database security. Keep your queries safe, and your data will thank you! ๐Ÿš€


Happy secure coding! ๐ŸŽ‰๐Ÿ”’โœจ