Уязвимости, SQL-инъекции, XSS, секреты в коде, валидация данных
Безопасность — это не фича, а обязательное требование. Один пропущенный баг может стоить компании миллионов.
Проблема: конкатенация пользовательского ввода с SQL-запросом.
# ❌ УЯЗВИМО: SQL-инъекция
user_id = request.GET.get('id')
query = f"SELECT * FROM users WHERE id = {user_id}" # Опасно!
cursor.execute(query)
# Атака: id = "1 OR 1=1" → вернёт всех пользователей
# ✅ Безопасно: параметризованный запрос
user_id = request.GET.get('id')
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
# ✅ Или через ORM (безопасно по умолчанию)
user = User.objects.get(id=user_id)Что проверять:
Проблема: вывод пользовательских данных без экранирования.
# ❌ УЯЗВИМО: XSS
@app.route('/comment')
def show_comment():
comment = request.args.get('text')
return f"<div>{comment}</div>" # Опасно!
# Атака: text = "<script>alert('xss')</script>"
# ✅ Безопасно: экранирование
from markupsafe import escape
@app.route('/comment')
def show_comment():
comment = request.args.get('text')
return f"<div>{escape(comment)}</div>"
# ✅ Или через шаблонизатор (автоматическое экранирование)
return render_template('comment.html', comment=comment)Что проверять:
innerHTML с непроверенными даннымиПроблема: отсутствие проверки прав доступа.
# ❌ УЯЗВИМО: нет проверки прав
@app.route('/user/<int:user_id>/profile')
def get_profile(user_id):
user = User.query.get(user_id)
return jsonify(user.data) # Любой может смотреть чужие профили!
# ✅ Безопасно: проверка прав
from flask_login import current_user
@app.route('/user/<int:user_id>/profile')
def get_profile(user_id):
if current_user.id != user_id and not current_user.is_admin:
abort(403)
user = User.query.get(user_id)
return jsonify(user.data)Что проверять:
# ❌ УЯЗВИМО: секрет в коде
API_KEY = "sk_live_abc123xyz"
DATABASE_URL = "postgresql://user:password@host/db"
# ✅ Безопасно: переменные окружения
import os
API_KEY = os.environ.get('API_KEY')
DATABASE_URL = os.environ.get('DATABASE_URL')Что проверять:
.env (не в git!) или secret manager.env в .gitignore# ❌ УЯЗВИМО: логирование пароля
logger.info(f"User login: {username}, password: {password}")
# ❌ УЯЗВИМО: логирование токена
logger.debug(f"API request with token: {auth_token}")
# ✅ Безопасно: маскирование
logger.info(f"User login attempt: {username}")
logger.debug(f"API request with token: {auth_token[:4]}...")Что проверять:
# ❌ УЯЗВИМО: нет валидации
def transfer_money(from_account, to_account, amount):
# amount может быть отрицательным или строкой!
from_account.balance -= amount
to_account.balance += amount
# ✅ Безопасно: валидация
from decimal import Decimal
def transfer_money(from_account, to_account, amount):
if not isinstance(amount, (int, float, Decimal)):
raise ValueError("Amount must be a number")
if amount <= 0:
raise ValueError("Amount must be positive")
if amount > from_account.balance:
raise ValueError("Insufficient funds")
from_account.balance -= amount
to_account.balance += amountЧек-лист валидации:
# ❌ УЯЗВИМО: path traversal
@app.route('/file')
def get_file():
filename = request.args.get('name')
return open(f'/var/data/{filename}').read()
# Атака: name = "../../etc/passwd"
# ✅ Безопасно: валидация пути
from pathlib import Path
import os
@app.route('/file')
def get_file():
filename = request.args.get('name')
base_dir = Path('/var/data').resolve()
file_path = (base_dir / filename).resolve()
# Проверка, что путь внутри base_dir
if not str(file_path).startswith(str(base_dir)):
abort(403)
return file_path.read_text()# ❌ УЯЗВИМО: нет требований к паролю
def register(username, password):
user = User(username=username, password=password)
user.save()
# ✅ Безопасно: требования к паролю
import re
def is_strong_password(password):
if len(password) < 12:
return False
if not re.search(r'[A-Z]', password):
return False
if not re.search(r'[a-z]', password):
return False
if not re.search(r'\d', password):
return False
return True
def register(username, password):
if not is_strong_password(password):
raise ValueError("Weak password")
# Хэширование пароля
from werkzeug.security import generate_password_hash
hashed = generate_password_hash(password)
user = User(username=username, password=hashed)
user.save()Что проверять:
# ❌ УЯЗВИМО: сессия не сбрасывается после логина
@app.route('/login', methods=['POST'])
def login():
user = authenticate(request.form)
if user:
session['user_id'] = user.id # Старая сессия!
return redirect('/dashboard')
# ✅ Безопасно: регенерация сессии
@app.route('/login', methods=['POST'])
def login():
user = authenticate(request.form)
if user:
session.clear() # Очистить старую сессию
session.regenerate() # Создать новую
session['user_id'] = user.id
return redirect('/dashboard')# Проверка уязвимостей
poetry check
pip-audit
safety check
# Обновление зависимостей
poetry update
pip install --upgrade packageЧто проверять:
| Инструмент | Что проверяет |
|---|---|
| bandit | Уязвимости в Python коде |
| safety | Уязвимости в зависимостях |
| pip-audit | Уязвимости в зависимостях |
| snyk | Уязвимости в коде и зависимостях |
| OWASP ZAP | Сканирование веб-приложений |
# Запуск проверок
bandit -r myapp/
safety check
pip-auditКлючевая мысль: Безопасность — это процесс, а не результат. Проверяйте каждый PR на основные уязвимости: SQL injection, XSS, access control, secrets в коде.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.