SQL Injection: уязвимости и защита (prepared statements, параметризация)
Что такое SQL-инъекция и почему она опасна? 🔓
SQL-инъекция — это уязвимость, которая позволяет злоумышленнику вмешиваться в SQL-запросы вашего приложения. Представьте, что ваш код превращается в послушную марионетку в руках хакера!
Пример уязвимого кода на Python (с SQLite):
username = input("Введите логин: ")
password = input("Введите пароль: ")
# ⚠️ Уязвимый запрос! Никогда так не делайте!
query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
cursor.execute(query)
Если ввести admin' -- как логин, запрос превратится в:
SELECT * FROM users WHERE username = 'admin' --' AND password = '...'
Комментарий -- отключает проверку пароля, и злоумышленник входит как админ!
Как работают Prepared Statements 🛡️
Prepared Statements (подготовленные выражения) — это самый надёжный способ защиты. Вместо вставки данных напрямую в запрос, мы используем параметры.
Исправленный безопасный пример:
username = input("Введите логин: ")
password = input("Введите пароль: ")
# ✅ Безопасный запрос с параметрами
query = "SELECT * FROM users WHERE username = ? AND password = ?"
cursor.execute(query, (username, password))
Почему это безопасно? База данных:
- Сначала компилирует шаблон запроса
- Затем подставляет значения как данные (не как часть кода)
Параметризация в разных СУБД 📊
Принцип везде одинаков, но синтаксис отличается:
MySQL (Python + mysql-connector):
query = "INSERT INTO products (name, price) VALUES (%s, %s)"
cursor.execute(query, ("Телевизор", 39900))
PostgreSQL (Psycopg2):
query = "UPDATE employees SET salary = %s WHERE id = %s"
cursor.execute(query, (75000, 42))
SQL Server (pyodbc):
query = "DELETE FROM orders WHERE id = ?"
cursor.execute(query, (1001,))
Что ещё важно знать о защите? 🔍
- Никогда не доверяйте пользовательскому вводу — даже данные из cookies или headers могут быть опасны.
- Используйте ORM с умом — Django ORM, SQLAlchemy автоматически экранируют запросы, но raw SQL требует осторожности.
- Ограничивайте права — у приложения должны быть только необходимые права в БД (не root!).
Пример опасного raw-запроса в Django:
# ⚠️ Уязвимость!
User.objects.raw(f"SELECT * FROM auth_user WHERE username = '{username}'")
Безопасная альтернатива:
User.objects.raw("SELECT * FROM auth_user WHERE username = %s", [username])
Практическая демонстрация атаки 💻
Давайте смоделируем взлом:
# Уязвимое приложение
def unsafe_login():
username = input("Логин: ")
password = input("Пароль: ")
cursor.execute(f"SELECT * FROM users WHERE login='{username}' AND pass='{password}'")
return cursor.fetchone()
# Вводим:
# Логин: admin' --
# Пароль: что угодно
Результат: Получаем доступ к учётной записи admin без пароля!
Главные выводы 🎯
- Всегда используйте параметризованные запросы
- Никогда не конкатенируйте SQL-строки вручную
- Проводите аудит безопасности своего кода
- Изучайте документацию вашей СУБД по теме защиты
Теперь вы вооружены знаниями, чтобы делать свои приложения неуязвимыми! 💪