Блог AST-SoftPro
Безопасность Python-приложений: OWASP Top 10 и защита
Безопасность Python-приложений: OWASP Top 10 и защита
Разработка веб-приложений на языке Python сегодня остаётся одной из самых популярных практик. Благодаря простоте синтаксиса, богатству экосистемы (включая Django, Flask, FastAPI) и высокой скорости разработки, Python активно используется в создании как простых, так и масштабируемых решений.
Однако популярность языка напрямую связана с ростом числа атак на приложения, построенные на его основе. Среди наиболее распространённых уязвимостей — те, что перечислены в OWASP Top 10: наборе критических рисков для веб-приложений, обновлённом ежегодно организацией Open Web Application Security Project (OWASP).
В этой статье рассматриваются ключевые угрозы из списка OWASP Top 2021 года, особенно актуальные для Python-проектов, и предлагаются практические меры по их предотвращению: защита от SQL-инъекций, XSS, CSRF, корректная реализация аутентификации с использованием JWT.
Уязвимости в OWASP Top 10: что важно знать разработчику на Python
OWASP Top 2021 включает десять наиболее критичных уязвимостей. Ниже — те из них, которые чаще всего встречаются в приложениях на Python и требуют особого внимания при разработке.
A01:2021 – Breaking Authentication (Аутентификация без должной защиты)
Описание: Уязвимости в системе аутентификации позволяют злоумышленникам получать доступ к учётным записям, обходя или подделывая механизмы проверки личности. Это может привести к полной компрометации приложения.
Примеры уязвимых сценариев на Python:
-
Хранение паролей без хеширования (например, через user.password == 'secret123')
-
Отсутствие rate limiting при входе
-
Использование устаревших или слабых алгоритмов аутентификации
Рекомендации по защите:
-
Всегда шифровать пароли с помощью bcrypt, scrypt или Argon2 (bcrypt.hashpw()).
-
Реализовать двухфакторную аутентификацию (2FA) через TOTP.
-
Ограничить количество попыток входа за минуту (rate limiting).
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def verify_password(plain_pwd, hashed):
return pwd_context.verify(plain_pwd, hashed)
A02:2021 – Crypto Issues (Проблемы с криптографией)
Описание: Неправильное использование криптографии — одна из самых опасных уязвимостей. Даже простая ошибка может привести к утечке данных или возможности подделки сообщений.
Пример ошибки в Python:
import hashlib token = hashlib.md5(payload + secret_key).hexdigest() # Опасно! MD5 и простое сложение — не защита!
Рекомендации:
-
Не использовать встроенные криптографические функции вне специализированных библиотек.
-
Применять только проверенные алгоритмы из cryptography, hmac, os.urandom.
-
Избегать «самописной» реализации HMAC, если нет глубокого понимания протокола.
import hmac from os import urandom key = urandom(32) signature = hmac.new(key, message.encode(), 'sha256').hexdigest()
A03:2021 – Injection (Инъекции)
Описание: Ввод данных пользователем используется напрямую в командных строках, SQL-запросах или других исполняемых конструкциях — это позволяет злоумышленнику выполнить произвольный код.
Подкатегории:
-
SQL-инъекция
-
Command injection
-
LDAP/NoSQL injection
A04:2021 – Insecure Design (Небезопасное проектирование)
Описание: Приложение строится без учёта принципов безопасности на этапе проектирования. Это может проявляться в избыточной открытости API, отсутствии разделения привилегий или игнорировании принципа минимальных прав.
Пример из практики Python-роутинга:
@app.route('/admin/<user_id>')
def admin_panel(user_id):
return get_admin_data(user_id) # Доступ к админке без проверки прав!
Рекомендации:
-
Реализовать RBAC (Role-Based Access Control).
-
Проверять права пользователя перед выполнением операций.
-
Минимизировать количество точек входа в критические функции.
A05:2021 – Security Misconfiguration
Описание: Ошибки конфигурации — одна из самых частых причин утечек. Это может быть включённый отладочный режим, незашифрованное соединение (HTTP вместо HTTPS), открытые API-ключи или ненужные расширения в зависимости.
Частые ошибки на Python:
-
Запуск Flask/FastAPI без debug=False в production.
-
Использование .env с секретами вне контроля версий (git add .env).
-
Открытие /var/log/access.log для публичного доступа.
Рекомендации по конфигурации:
# app.py — НЕПРАВИЛЬНО (в продакшене)
app.run(debug=True)
# Правильно:
if __name__ == '__main__':
import os
debug = not os.getenv('PRODUCTION', 'false').lower() in ('yes','true')
print(f"Running in {'development' if debug else 'production'} mode")
app.run(debug=debug, host='0.0.0.0', port=int(os.environ.get("PORT", 5000)))
Защита от SQL-инъекций: лучшие практики
SQL-инъекции — одна из самых распространённых уязвимостей в Python-приложениях, особенно при использовании sqlite3, psycopg2 или других прямых подключений к БД.
Проблемный код:
import sqlite3
def get_user(username):
conn = sqlite3.connect('db.sqlite')
cursor = conn.cursor()
query = f"SELECT * FROM users WHERE username='{username}'"
cursor.execute(query)
return cursor.fetchone() # Уязвимо к SQLi!
Решение: Использование параметризованных запросов.
def get_user(username):
conn = sqlite3.connect('db.sqlite')
query = "SELECT * FROM users WHERE username=?"
cursor = conn.cursor()
cursor.execute(query, (username,))
return cursor.fetchone() # Защита от SQLi!
Дополнительно:
-
Использовать ORM (SQLAlchemy, Django ORM) — они по умолчанию безопасны.
-
Применять подготовленные выражения (prepared statements) везде.
Проверка на наличие уязвимостей:
Используйте SAST-инструменты:
| Инструмент | Поддержка Python |
|---|---|
| Bandit | ✅ Да |
| pycloc | ❌ Нет |
pip install bandit bandit -r ./myapp/ --confidence HIGH
Защита от XSS (Cross-Site Scripting)
XSS позволяет злоумышленнику внедрять вредоносный JavaScript в страницу, которую видит жертва. Часто встречается при выводе пользовательского ввода без экранирования.
Пример уязвимости:
# Шаблон Jinja2 — НЕПРАВИЛЬНО!
<title>{{ user_input }}</title>
Если user_input = '<script>alert(1)</script>', браузер выполнит код.
Решение: Экранирование вывода.
from jinja2 import Template
template = Template("<title>{{ input | e }}</title>") # 'e' — escape фильтр
rendered = template.render(input='<script>alert(1)</script>')
# Результат: <title><script>alert(1)</script></title>
Рекомендации:
-
Всегда использовать фильтры экранирования (| e) в шаблонах.
-
Избегать innerHTML без проверки.
-
Проверять и очищать пользовательский контент (например, через bleach).
import bleach
clean_input = bleach.clean(user_input,
tags=[],
attributes={},
strip=True)
Защита от CSRF (Cross-Site Request Forgery)
CSRF позволяет злоумышленнику выполнить действие от имени авторизованного пользователя без его ведома. Например, отправить форму или изменить настройки.
Пример уязвимого кода на Flask:
@app.route('/transfer', methods=['POST'])
def transfer():
amount = request.form['amount']
if user.is_authenticated:
process_transfer(amount) # Нет проверки токена!
Решение: Использование CSRF-токенов.
Вариант 1: Flask-WTF (рекомендуется)
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
class TransferForm(FlaskForm):
amount = StringField('Amount')
submit = SubmitField('Transfer')
@app.route('/transfer', methods=['GET', 'POST'])
def transfer():
form = TransferForm()
if form.validate_on_submit():
process_transfer(form.amount.data)
return redirect('/success')
return render_template('form.html', form=form)
Вариант 2: Ручное добавление токена
import secrets
@app.before_request
def set_csrf_token():
if request.method == 'POST' and not session.get('_csrf'):
token = secrets.token_hex(16)
session['_csrf'] = token
# Проверка в view:
def transfer():
csrf = session.pop('_csrf', None)
submitted_csrf = request.form.get('csrf')
if csrf != submitted_csrf or not user.is_authenticated:
return abort(403)
process_transfer(request.form['amount'])
Аутентификация и JWT: как делать правильно
JWT (JSON Web Token) — популярный способ передачи токенов аутентификации. Однако его неправильное использование может привести к критическим уязвимостям.
A08:2021 – Broken Access Control
Описание: Возможность получить доступ к ресурсам, для которых пользователь не имеет прав.
Пример ошибки:
@app.route('/profile/<user_id>', methods=['GET'])
def get_profile(user_id):
return user_data[int(user_id)] # Любой может прочитать любой профиль!
Решение: Проверка владельца или роли.
from functools import wraps
def requires_role(role='USER'):
def wrapper(f):
@wraps(f)
def decorated(*args, **kwargs):
token = request.headers.get('Authorization')
# Парсинг и проверка JWT (упрощённо!)
payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
if payload.get('role') != role:
return abort(403)
return f(*args, **kwargs)
return decorated
return wrapper
@app.route('/profile/<int:user_id>')
@requires_role('ADMIN')
def get_profile(user_id):
return user_data[user_id]
Рекомендации по JWT:
-
Никогда не передавать конфиденциальные данные в payload.
-
Использовать короткие сроки жизни (short-lived tokens).
-
Поддерживать refresh-токены отдельно от access-токенов.
-
Хранить токены только на клиенте — без localStorage, если возможно.
Дополнительные меры по безопасности Python-приложений
Помимо конкретных уязвимостей из OWASP Top 10, важно соблюдать общие практики:
Проверка зависимостей (Dependency Scanning)
Устаревшие или известные уязвимые библиотеки — частая причина инцидентов.
Инструменты:
| Инструмент | Описание |
|---|---|
| pip-audit | Анализирует зависимости на наличие CVE |
| safety check | Проверяет репозиторий перед пушем |
# Установка безопасности зависимостей pip install safety # Проверка проекта safety check --full-report > report.json
Логирование и мониторинг
Не логируйте пароли, токены или личные данные.
Пример безопасного лога:
import logging
logger = logging.getLogger('secure')
logger.info("User %d attempted login from IP %s", user.id, request.remote_addr)
# ❌ Не делать: logger.error(f"Failed login with password {password}")
Защита API
-
Валидируйте все входные данные (не только тип, но и формат).
-
Используйте rate limiting (flask-limiter или gunicorn + Redis).
-
Настройте CORS безопасно:
# flask-cors — НЕПРАВИЛЬНО:
CORS(app)
# Правильно: разрешить только нужные домены
from flask_cors import CORS
cors = CORS(app, resources={r"/api/*": {"origins": "https://trusted-app.com"}})
Заключение
Безопасность Python-приложений — не опция, а обязательное требование. Даже небольшие ошибки в коде могут привести к серьёзным последствиям: утечке данных, компрометации аккаунтов или остановке бизнеса клиента.
Ключевые шаги:
-
Изучайте OWASP Top 10 и применяйте рекомендации для Python.
-
Используйте безопасные практики уже на этапе проектирования (security by design).
-
Автоматизируйте проверку кода: SAST, DAST, dependency scanning.
-
Регулярно обновляйте зависимости и фреймворки.
-
Проводите внутренние аудиты безопасности — даже если приложение кажется «простым».
Помните: безопасность — это процесс, а не разовое действие. Даже самая надёжная библиотека может быть скомпрометирована через ошибку в вашем коде.